git.iko.soy/typeable/minio-hs
on ae141fd help sign in
.github/workflows/ci.ymldeleted−266
since splitsnapshots
unifiedsplit
@@ -1,266 +0,0 @@
1−name: CI
2−
3−# Trigger the workflow on push or pull request, but only for the master branch
4−on:
5− pull_request:
6− branches: [master]
7− push:
8− branches: [master]
9−
10−# This ensures that previous jobs for the PR are canceled when the PR is
11−# updated.
12−concurrency:
13− group: ${{ github.workflow }}-${{ github.head_ref }}
14− cancel-in-progress: true
15−
16−# Env vars for tests
17−env:
18− MINIO_ACCESS_KEY: minio
19− MINIO_SECRET_KEY: minio123
20− MINIO_LOCAL: 1
21−
22−jobs:
23− ormolu:
24− runs-on: ubuntu-latest
25− steps:
26− - uses: actions/checkout@v4
27− - uses: haskell-actions/run-ormolu@v15
28− with:
29− version: "0.5.0.1"
30−
31− hlint:
32− runs-on: ubuntu-latest
33− steps:
34− - uses: actions/checkout@v4
35−
36− - name: "Set up HLint"
37− uses: haskell-actions/hlint-setup@v2
38− with:
39− version: "3.5"
40−
41− - name: "Run HLint"
42− uses: haskell-actions/hlint-run@v2
43− with:
44− path: '["src/", "test/", "examples"]'
45− fail-on: warning
46−
47− cabal:
48− name: ${{ matrix.os }} / ghc-${{ matrix.ghc }} / cabal-${{ matrix.cabal }}
49− runs-on: ${{ matrix.os }}
50− needs: ormolu
51− strategy:
52− matrix:
53− os: [ubuntu-latest, windows-latest, macos-latest]
54− cabal: ["3.8", "latest"]
55− ghc:
56− - "9.8"
57− - "9.6"
58− - "9.4"
59− - "9.2"
60− - "9.0"
61− - "8.10"
62− exclude:
63− # macos llvm issue for versions less than 9.2
64− - os: macos-latest
65− ghc: "8.10"
66− - os: macos-latest
67− ghc: "9.0"
68− # Cabal 3.8 supports GHC < 9.6
69− - cabal: "3.8"
70− ghc: "9.6"
71− - cabal: "3.8"
72− ghc: "9.8"
73−
74− steps:
75− - uses: actions/checkout@v4
76− if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/master'
77−
78− - uses: haskell-actions/setup@v2
79− id: setup
80− name: Setup Haskell
81− with:
82− ghc-version: ${{ matrix.ghc }}
83− cabal-version: ${{ matrix.cabal }}
84− cabal-update: true
85−
86− - name: Configure
87− run: |
88− cabal configure --enable-tests --enable-benchmarks --test-show-details=direct -fexamples -fdev -flive-test
89− cabal build all --dry-run
90− # The last step generates dist-newstyle/cache/plan.json for the cache key.
91−
92− - name: Restore cached dependencies
93− uses: actions/cache/restore@v4
94− id: cache
95− env:
96− key: ${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-cabal-${{ steps.setup.outputs.cabal-version }}
97− with:
98− path: ${{ steps.setup.outputs.cabal-store }}
99− key: ${{ env.key }}-plan-${{ hashFiles('**/plan.json') }}
100− restore-keys: ${{ env.key }}-
101−
102− - name: Install dependencies
103− # If we had an exact cache hit, the dependencies will be up to date.
104− if: steps.cache.outputs.cache-hit != 'true'
105− run: cabal build all --only-dependencies
106−
107− # Cache dependencies already here, so that we do not have to rebuild them should the subsequent steps fail.
108− - name: Save cached dependencies
109− uses: actions/cache/save@v4
110− # If we had an exact cache hit, trying to save the cache would error because of key clash.
111− if: steps.cache.outputs.cache-hit != 'true'
112− with:
113− path: ${{ steps.setup.outputs.cabal-store }}
114− key: ${{ steps.cache.outputs.cache-primary-key }}
115−
116− - name: Build
117− run: |
118− cabal build all
119−
120− - name: Setup TLS certs for MinIO for testing (Linux)
121− if: matrix.os == 'ubuntu-latest'
122− run: |
123− mkdir -p /tmp/minio /tmp/minio-config/certs
124− cp test/cert/* /tmp/minio-config/certs/
125− (cd /tmp/minio; wget -q https://dl.min.io/server/minio/release/linux-amd64/minio; chmod +x ./minio)
126− sudo cp /tmp/minio-config/certs/public.crt /usr/local/share/ca-certificates/
127− sudo update-ca-certificates
128−
129− ## Currently disable TLS setup for MacOS due to issues in trusting it on MacOS.
130− - name: Setup TLS certs for MinIO for testing (MacOS)
131− if: matrix.os == 'macos-latest'
132− run: |
133− mkdir -p /tmp/minio /tmp/minio-config/certs
134− cp test/cert/* /tmp/minio-config/certs/
135− (cd /tmp/minio; wget -q https://dl.min.io/server/minio/release/darwin-amd64/minio; chmod +x ./minio)
136− # sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/minio-config/certs/public.crt
137−
138− - name: Setup MinIO for testing (Windows)
139− if: matrix.os == 'windows-latest'
140− run: |
141− New-Item -ItemType Directory -Path "$env:temp/minio-config/certs/"
142− Copy-Item -Path test\cert\* -Destination "$env:temp/minio-config/certs/"
143− Invoke-WebRequest -Uri https://dl.minio.io/server/minio/release/windows-amd64/minio.exe -OutFile $HOME/minio.exe
144− Import-Certificate -FilePath "$env:temp/minio-config/certs/public.crt" -CertStoreLocation Cert:\LocalMachine\Root
145−
146− - name: Test (Linux)
147− if: matrix.os == 'ubuntu-latest'
148− env:
149− MINIO_SECURE: 1
150− run: |
151− /tmp/minio/minio server --quiet --certs-dir /tmp/minio-config/certs data1 data2 data3 data4 2>&1 > minio.log &
152− ghc --version
153− cabal --version
154− cabal test all
155−
156− - name: Test (MacOS)
157− if: matrix.os == 'macos-latest'
158− # # Leave MINIO_SECURE unset to disable TLS in tests.
159− # env:
160− # MINIO_SECURE: 1
161− run: |
162− /tmp/minio/minio server --quiet data1 data2 data3 data4 2>&1 > minio.log &
163− ghc --version
164− cabal --version
165− cabal test all
166−
167− - name: Test (Windows)
168− if: matrix.os == 'windows-latest'
169− env:
170− MINIO_SECURE: 1
171− run: |
172− Start-Process -NoNewWindow -FilePath "$HOME/minio.exe" -ArgumentList "--certs-dir", "$env:temp/minio-config/certs", "server", "$env:temp/data1", "$env:temp/data2", "$env:temp/data3", "$env:temp/data4"
173− ghc --version
174− cabal --version
175− cabal test all
176−
177− stack:
178− name: stack / ghc ${{ matrix.ghc }}
179− runs-on: ${{ matrix.os }}
180− env:
181− MINIO_SECURE: 1
182− strategy:
183− matrix:
184− ghc:
185− - "8.10.7"
186− - "9.0.2"
187− - "9.2.8"
188− - "9.4.8"
189− - "9.6.5"
190− - "9.8.2"
191− os: [ubuntu-latest]
192−
193− steps:
194− - uses: actions/checkout@v4
195− if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/master'
196−
197− - uses: haskell-actions/setup@v2
198− with:
199− ghc-version: ${{ matrix.ghc }}
200− enable-stack: true
201− stack-version: "latest"
202−
203− - uses: actions/cache@v4
204− name: Cache ~/.stack
205− with:
206− path: ~/.stack
207− key: ${{ runner.os }}-stack-global-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
208− restore-keys: |
209− ${{ runner.os }}-stack-global-
210− - uses: actions/cache@v4
211− name: Cache .stack-work
212− with:
213− path: .stack-work
214− key: ${{ runner.os }}-stack-work-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}-${{ hashFiles('**/*.hs') }}
215− restore-keys: |
216− ${{ runner.os }}-stack-work-
217−
218− - name: Install dependencies
219− run: |
220− stack --version
221− stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks --only-dependencies
222−
223− - name: Build
224− run: |
225− stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks --flag minio-hs:examples --flag minio-hs:live-test --flag minio-hs:dev
226−
227− - name: Setup MinIO for testing (Linux)
228− if: matrix.os == 'ubuntu-latest'
229− run: |
230− mkdir -p /tmp/minio /tmp/minio-config/certs
231− cp test/cert/* /tmp/minio-config/certs/
232− (cd /tmp/minio; wget -q https://dl.min.io/server/minio/release/linux-amd64/minio; chmod +x ./minio)
233− sudo cp /tmp/minio-config/certs/public.crt /usr/local/share/ca-certificates/
234− sudo update-ca-certificates
235−
236− - name: Setup MinIO for testing (MacOS)
237− if: matrix.os == 'macos-latest'
238− run: |
239− mkdir -p /tmp/minio /tmp/minio-config/certs
240− cp test/cert/* /tmp/minio-config/certs/
241− (cd /tmp/minio; wget -q https://dl.min.io/server/minio/release/darwin-amd64/minio; chmod +x ./minio)
242− sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/minio-config/certs/public.crt
243−
244− - name: Setup MinIO for testing (Windows)
245− if: matrix.os == 'windows-latest'
246− run: |
247− New-Item -ItemType Directory -Path "$env:temp/minio-config/certs/"
248− Copy-Item -Path test\cert\* -Destination "$env:temp/minio-config/certs/"
249− Invoke-WebRequest -Uri https://dl.minio.io/server/minio/release/windows-amd64/minio.exe -OutFile $HOME/minio.exe
250− Import-Certificate -FilePath "$env:temp/minio-config/certs/public.crt" -CertStoreLocation Cert:\LocalMachine\Root
251−
252− - name: Test (Non-Windows)
253− if: matrix.os != 'windows-latest'
254− run: |
255− /tmp/minio/minio server --quiet --certs-dir /tmp/minio-config/certs data1 data2 data3 data4 2>&1 > minio.log &
256− ghc --version
257− stack --version
258− stack test --system-ghc --flag minio-hs:live-test --flag minio-hs:dev
259−
260− - name: Test (Windows)
261− if: matrix.os == 'windows-latest'
262− run: |
263− Start-Process -NoNewWindow -FilePath "$HOME/minio.exe" -ArgumentList "--certs-dir", "$env:temp/minio-config/certs", "server", "$env:temp/data1", "$env:temp/data2", "$env:temp/data3", "$env:temp/data4"
264− ghc --version
265− cabal --version
266− stack test --system-ghc --flag minio-hs:live-test --flag minio-hs:dev
.stylish-haskell.yamlnew file+233
@@ -0,0 +1,233 @@
1+# stylish-haskell configuration file
2+# ==================================
3+
4+# The stylish-haskell tool is mainly configured by specifying steps. These steps
5+# are a list, so they have an order, and one specific step may appear more than
6+# once (if needed). Each file is processed by these steps in the given order.
7+steps:
8+ # Convert some ASCII sequences to their Unicode equivalents. This is disabled
9+ # by default.
10+ # - unicode_syntax:
11+ # # In order to make this work, we also need to insert the UnicodeSyntax
12+ # # language pragma. If this flag is set to true, we insert it when it's
13+ # # not already present. You may want to disable it if you configure
14+ # # language extensions using some other method than pragmas. Default:
15+ # # true.
16+ # add_language_pragma: true
17+
18+ # Align the right hand side of some elements. This is quite conservative
19+ # and only applies to statements where each element occupies a single
20+ # line.
21+ - simple_align:
22+ cases: true
23+ top_level_patterns: true
24+ records: true
25+
26+ # Import cleanup
27+ - imports:
28+ # There are different ways we can align names and lists.
29+ #
30+ # - global: Align the import names and import list throughout the entire
31+ # file.
32+ #
33+ # - file: Like global, but don't add padding when there are no qualified
34+ # imports in the file.
35+ #
36+ # - group: Only align the imports per group (a group is formed by adjacent
37+ # import lines).
38+ #
39+ # - none: Do not perform any alignment.
40+ #
41+ # Default: global.
42+ align: global
43+
44+ # The following options affect only import list alignment.
45+ #
46+ # List align has following options:
47+ #
48+ # - after_alias: Import list is aligned with end of import including
49+ # 'as' and 'hiding' keywords.
50+ #
51+ # > import qualified Data.List as List (concat, foldl, foldr, head,
52+ # > init, last, length)
53+ #
54+ # - with_alias: Import list is aligned with start of alias or hiding.
55+ #
56+ # > import qualified Data.List as List (concat, foldl, foldr, head,
57+ # > init, last, length)
58+ #
59+ # - new_line: Import list starts always on new line.
60+ #
61+ # > import qualified Data.List as List
62+ # > (concat, foldl, foldr, head, init, last, length)
63+ #
64+ # Default: after_alias
65+ list_align: after_alias
66+
67+ # Right-pad the module names to align imports in a group:
68+ #
69+ # - true: a little more readable
70+ #
71+ # > import qualified Data.List as List (concat, foldl, foldr,
72+ # > init, last, length)
73+ # > import qualified Data.List.Extra as List (concat, foldl, foldr,
74+ # > init, last, length)
75+ #
76+ # - false: diff-safe
77+ #
78+ # > import qualified Data.List as List (concat, foldl, foldr, init,
79+ # > last, length)
80+ # > import qualified Data.List.Extra as List (concat, foldl, foldr,
81+ # > init, last, length)
82+ #
83+ # Default: true
84+ pad_module_names: true
85+
86+ # Long list align style takes effect when import is too long. This is
87+ # determined by 'columns' setting.
88+ #
89+ # - inline: This option will put as much specs on same line as possible.
90+ #
91+ # - new_line: Import list will start on new line.
92+ #
93+ # - new_line_multiline: Import list will start on new line when it's
94+ # short enough to fit to single line. Otherwise it'll be multiline.
95+ #
96+ # - multiline: One line per import list entry.
97+ # Type with constructor list acts like single import.
98+ #
99+ # > import qualified Data.Map as M
100+ # > ( empty
101+ # > , singleton
102+ # > , ...
103+ # > , delete
104+ # > )
105+ #
106+ # Default: inline
107+ long_list_align: inline
108+
109+ # Align empty list (importing instances)
110+ #
111+ # Empty list align has following options
112+ #
113+ # - inherit: inherit list_align setting
114+ #
115+ # - right_after: () is right after the module name:
116+ #
117+ # > import Vector.Instances ()
118+ #
119+ # Default: inherit
120+ empty_list_align: inherit
121+
122+ # List padding determines indentation of import list on lines after import.
123+ # This option affects 'long_list_align'.
124+ #
125+ # - <integer>: constant value
126+ #
127+ # - module_name: align under start of module name.
128+ # Useful for 'file' and 'group' align settings.
129+ list_padding: 4
130+
131+ # Separate lists option affects formatting of import list for type
132+ # or class. The only difference is single space between type and list
133+ # of constructors, selectors and class functions.
134+ #
135+ # - true: There is single space between Foldable type and list of it's
136+ # functions.
137+ #
138+ # > import Data.Foldable (Foldable (fold, foldl, foldMap))
139+ #
140+ # - false: There is no space between Foldable type and list of it's
141+ # functions.
142+ #
143+ # > import Data.Foldable (Foldable(fold, foldl, foldMap))
144+ #
145+ # Default: true
146+ separate_lists: true
147+
148+ # Space surround option affects formatting of import lists on a single
149+ # line. The only difference is single space after the initial
150+ # parenthesis and a single space before the terminal parenthesis.
151+ #
152+ # - true: There is single space associated with the enclosing
153+ # parenthesis.
154+ #
155+ # > import Data.Foo ( foo )
156+ #
157+ # - false: There is no space associated with the enclosing parenthesis
158+ #
159+ # > import Data.Foo (foo)
160+ #
161+ # Default: false
162+ space_surround: false
163+
164+ # Language pragmas
165+ - language_pragmas:
166+ # We can generate different styles of language pragma lists.
167+ #
168+ # - vertical: Vertical-spaced language pragmas, one per line.
169+ #
170+ # - compact: A more compact style.
171+ #
172+ # - compact_line: Similar to compact, but wrap each line with
173+ # `{-#LANGUAGE #-}'.
174+ #
175+ # Default: vertical.
176+ style: vertical
177+
178+ # Align affects alignment of closing pragma brackets.
179+ #
180+ # - true: Brackets are aligned in same column.
181+ #
182+ # - false: Brackets are not aligned together. There is only one space
183+ # between actual import and closing bracket.
184+ #
185+ # Default: true
186+ align: true
187+
188+ # stylish-haskell can detect redundancy of some language pragmas. If this
189+ # is set to true, it will remove those redundant pragmas. Default: true.
190+ remove_redundant: true
191+
192+ # Replace tabs by spaces. This is disabled by default.
193+ # - tabs:
194+ # # Number of spaces to use for each tab. Default: 8, as specified by the
195+ # # Haskell report.
196+ # spaces: 8
197+
198+ # Remove trailing whitespace
199+ - trailing_whitespace: {}
200+
201+# A common setting is the number of columns (parts of) code will be wrapped
202+# to. Different steps take this into account. Default: 80.
203+columns: 80
204+
205+# By default, line endings are converted according to the OS. You can override
206+# preferred format here.
207+#
208+# - native: Native newline format. CRLF on Windows, LF on other OSes.
209+#
210+# - lf: Convert to LF ("\n").
211+#
212+# - crlf: Convert to CRLF ("\r\n").
213+#
214+# Default: native.
215+newline: native
216+
217+# Sometimes, language extensions are specified in a cabal file or from the
218+# command line instead of using language pragmas in the file. stylish-haskell
219+# needs to be aware of these, so it can parse the file correctly.
220+#
221+# No language extensions are enabled by default.
222+language_extensions:
223+ - BangPatterns
224+ - FlexibleContexts
225+ - FlexibleInstances
226+ - MultiParamTypeClasses
227+ - MultiWayIf
228+ - NoImplicitPrelude
229+ - OverloadedStrings
230+ - RankNTypes
231+ - ScopedTypeVariables
232+ - TupleSections
233+ - TypeFamilies
.travis.ymlnew file+61
@@ -0,0 +1,61 @@
1+sudo: true
2+language: haskell
3+
4+git:
5+ depth: 5
6+
7+cabal: "3.0"
8+
9+cache:
10+ directories:
11+ - "$HOME/.cabal/store"
12+ - "$HOME/.stack"
13+ - "$TRAVIS_BUILD_DIR/.stack-work"
14+
15+matrix:
16+ include:
17+
18+ # Cabal
19+ - ghc: 8.4.4
20+ - ghc: 8.6.5
21+ - ghc: 8.8.2
22+
23+ # Stack
24+ - ghc: 8.6.5
25+ env: STACK_YAML="$TRAVIS_BUILD_DIR/stack.yaml"
26+
27+before_install:
28+ - sudo apt-get install devscripts
29+ - mkdir /tmp/minio /tmp/certs
30+ - (cd /tmp/minio; wget https://dl.min.io/server/minio/release/linux-amd64/minio; chmod +x ./minio)
31+ - (cd /tmp/certs; openssl req -newkey rsa:2048 -nodes -keyout private.key -x509 -days 36500 -out public.crt -subj "/C=US/ST=NRW/L=Earth/O=CompanyName/OU=IT/CN=localhost/emailAddress=email@example.com")
32+ - sudo cp /tmp/certs/public.crt /usr/local/share/ca-certificates/
33+ - sudo update-ca-certificates
34+ - MINIO_ACCESS_KEY=minio MINIO_SECRET_KEY=minio123 /tmp/minio/minio server --quiet --certs-dir /tmp/certs data 2>&1 > minio.log &
35+
36+install:
37+ - |
38+ if [ -z "$STACK_YAML" ]; then
39+ ghc --version
40+ cabal --version
41+ cabal new-update
42+ cabal new-build --enable-tests --enable-benchmarks -fexamples
43+ else
44+ # install stack
45+ curl -sSL https://get.haskellstack.org/ | sh
46+
47+ # build project with stack
48+ stack --version
49+ stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks --flag minio-hs:examples
50+ fi
51+
52+script:
53+ - |
54+ if [ -z "$STACK_YAML" ]; then
55+ MINIO_LOCAL=1 MINIO_SECURE=1 cabal new-test --enable-tests -flive-test
56+ else
57+ MINIO_LOCAL=1 MINIO_SECURE=1 stack test --system-ghc --flag minio-hs:live-test
58+ fi
59+
60+notifications:
61+ email: false
CHANGELOG.md−31
@@ -1,37 +1,6 @@
1Changelog
1Changelog
2==========
2==========
3
3
4−## Version 1.7.0 -- Unreleased
5−
6−* Fix data type `EventMessage` to not export partial fields (#179)
7−* Bump up min bound on time dep and fix deprecation warnings (#181)
8−* Add `dev` flag to cabal for building with warnings as errors (#182)
9−* Fix AWS region map (#185)
10−* Fix XML generator tests (#187)
11−* Add support for STS Assume Role API (#188)
12−
13−### Breaking changes in 1.7.0
14−
15−* `Credentials` type has been removed. Use `CredentialValue` instead.
16−* `Provider` type has been replaced with `CredentialLoader`.
17−* `EventMessage` data type is updated.
18−
19−## Version 1.6.0
20−
21−* HLint fixes - some types were changed to newtype (#173)
22−* Fix XML generation test for S3 SELECT (#161)
23−* Use region specific endpoints for AWS S3 in presigned Urls (#164)
24−* Replace protolude with relude and build with GHC 9.0.2 (#168)
25−* Support aeson 2 (#169)
26−* CI updates and code formatting changes with ormolu 0.5.0.0
27−
28−## Version 1.5.3
29−
30−* Fix windows build
31−* Fix support for Yandex Storage (#147)
32−* Fix for HEAD requests to S3/Minio (#155)
33−* Bump up some dependencies, new code formatting, Github CI, example fixes and other minor improvements.
34−
35## Version 1.5.2
4## Version 1.5.2
36
5
37* Fix region `us-west-2` for AWS S3 (#139)
6* Fix region `us-west-2` for AWS S3 (#139)
CNAMEdeleted−1
@@ -1 +0,0 @@
1−minio-hs.min.io
README.md+29−50
@@ -1,8 +1,10 @@
1−# MinIO Haskell Client SDK for Amazon S3 Compatible Cloud Storage [![CI](https://github.com/minio/minio-hs/actions/workflows/ci.yml/badge.svg)](https://github.com/minio/minio-hs/actions/workflows/ci.yml)[![Hackage](https://img.shields.io/hackage/v/minio-hs.svg)](https://hackage.haskell.org/package/minio-hs)[![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io)
1+# MinIO Client SDK for Haskell [![Build Status](https://travis-ci.org/minio/minio-hs.svg?branch=master)](https://travis-ci.org/minio/minio-hs)[![Hackage](https://img.shields.io/hackage/v/minio-hs.svg)](https://hackage.haskell.org/package/minio-hs)[![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io)
2
2
3−The MinIO Haskell Client SDK provides simple APIs to access [MinIO](https://min.io) and any Amazon S3 compatible object storage.
3+The MinIO Haskell Client SDK provides simple APIs to access [MinIO](https://min.io) and Amazon S3 compatible object storage server.
4
4
5−This guide assumes that you have a working [Haskell development environment](https://www.haskell.org/downloads/).
5+## Minimum Requirements
6+
7+- The Haskell [stack](https://docs.haskellstack.org/en/stable/README/)
6
8
7## Installation
9## Installation
8
10
@@ -10,35 +12,20 @@ This guide assumes that you have a working [Haskell development environment](htt
10
12
11Simply add `minio-hs` to your project's `.cabal` dependencies section or if you are using hpack, to your `package.yaml` file as usual.
13Simply add `minio-hs` to your project's `.cabal` dependencies section or if you are using hpack, to your `package.yaml` file as usual.
12
14
13−### Try it out in a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)
15+### Try it out directly with `ghci`
14−
15−#### For a cabal based environment
16−
17−Download the library source and change to the extracted directory:
18−
19−``` sh
20−$ cabal get minio-hs
21−$ cd minio-hs-1.6.0/ # directory name could be different
22−```
23−
24−Then load the `ghci` REPL environment with the library and browse the available APIs:
25−
26−``` sh
27−$ cabal repl
28−ghci> :browse Network.Minio
29−```
30−
31−#### For a stack based environment
32
16
33From your home folder or any non-haskell project directory, just run:
17From your home folder or any non-haskell project directory, just run:
34
18
35```sh
19```sh
20+
36stack install minio-hs
21stack install minio-hs
22+
37```
23```
38
24
39Then start an interpreter session and browse the available APIs with:
25Then start an interpreter session and browse the available APIs with:
40
26
41```sh
27```sh
28+
42$ stack ghci
29$ stack ghci
43> :browse Network.Minio
30> :browse Network.Minio
44```
31```
@@ -147,52 +134,44 @@ main = do
147
134
148### Development
135### Development
149
136
150−#### Download the source
137+To setup:
151
138
152```sh
139```sh
153−$ git clone https://github.com/minio/minio-hs.git
140+git clone https://github.com/minio/minio-hs.git
141+
154−$ cd minio-hs/
142+cd minio-hs/
155−```
156
143
157−#### Build the package:
144+stack install
145+```
158
146
159−With `cabal`:
147+Tests can be run with:
160
148
161```sh
149```sh
162−$ # Configure cabal for development enabling all optional flags defined by the package.
163−$ cabal configure --enable-tests --test-show-details=direct -fexamples -fdev -flive-test
164−$ cabal build
165−```
166
150
167−With `stack`:
151+stack test
168
152
169−``` sh
170−$ stack build --test --no-run-tests --flag minio-hs:live-test --flag minio-hs:dev --flag minio-hs:examples
171```
153```
172−#### Running tests:
173
154
174−A section of the tests use the remote MinIO Play server at `https://play.min.io` by default. For library development, using this remote server maybe slow. To run the tests against a locally running MinIO live server at `http://localhost:9000` with the credentials `access_key=minio` and `secret_key=minio123`, just set the environment `MINIO_LOCAL` to any value (and unset it to switch back to Play).
155+A section of the tests use the remote MinIO Play server at `https://play.min.io` by default. For library development, using this remote server maybe slow. To run the tests against a locally running MinIO live server at `http://localhost:9000`, just set the environment `MINIO_LOCAL` to any value (and unset it to switch back to Play).
175
156
176−With `cabal`:
157+To run the live server tests, set a build flag as shown below:
177
158
178```sh
159```sh
179−$ export MINIO_LOCAL=1 # to run live tests against local MinIO server
180−$ cabal test
181−```
182
160
183−With `stack`:
161+stack test --flag minio-hs:live-test
162+
163+# OR against a local MinIO server with:
164+
165+MINIO_LOCAL=1 stack test --flag minio-hs:live-test
184
166
185−``` sh
186−$ export MINIO_LOCAL=1 # to run live tests against local MinIO server
187−stack test --flag minio-hs:live-test --flag minio-hs:dev
188```
167```
189
168
190−This will run all the test suites.
169+The configured CI system always runs both test-suites for every change.
191
170
192−#### Building documentation:
171+Documentation can be locally built with:
193
172
194```sh
173```sh
195−$ cabal haddock
174+
196−$ # OR
197−$ stack haddock
175+stack haddock
176+
198```
177```
Setup.hsnew file+18
@@ -0,0 +1,18 @@
1+--
2+-- MinIO Haskell SDK, (C) 2017 MinIO, Inc.
3+--
4+-- Licensed under the Apache License, Version 2.0 (the "License");
5+-- you may not use this file except in compliance with the License.
6+-- You may obtain a copy of the License at
7+--
8+-- http://www.apache.org/licenses/LICENSE-2.0
9+--
10+-- Unless required by applicable law or agreed to in writing, software
11+-- distributed under the License is distributed on an "AS IS" BASIS,
12+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+-- See the License for the specific language governing permissions and
14+-- limitations under the License.
15+--
16+
17+import Distribution.Simple
18+main = defaultMain
examples/AssumeRole.hsdeleted−47
@@ -1,47 +0,0 @@
1−--
2−-- MinIO Haskell SDK, (C) 2023 MinIO, Inc.
3−--
4−-- Licensed under the Apache License, Version 2.0 (the "License");
5−-- you may not use this file except in compliance with the License.
6−-- You may obtain a copy of the License at
7−--
8−-- http://www.apache.org/licenses/LICENSE-2.0
9−--
10−-- Unless required by applicable law or agreed to in writing, software
11−-- distributed under the License is distributed on an "AS IS" BASIS,
12−-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13−-- See the License for the specific language governing permissions and
14−-- limitations under the License.
15−--
16−{-# LANGUAGE OverloadedStrings #-}
17−
18−import Control.Monad.IO.Class (liftIO)
19−import Network.Minio
20−import Prelude
21−
22−main :: IO ()
23−main = do
24− -- Use play credentials for example.
25− let assumeRole =
26− STSAssumeRole
27− ( CredentialValue
28− "Q3AM3UQ867SPQQA43P2F"
29− "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
30− Nothing
31− )
32− $ defaultSTSAssumeRoleOptions
33− { saroLocation = Just "us-east-1",
34− saroEndpoint = Just "https://play.min.io:9000"
35− }
36−
37− -- Retrieve temporary credentials and print them.
38− cv <- requestSTSCredential assumeRole
39− print $ "Temporary credentials" ++ show (credentialValueText $ fst cv)
40− print $ "Expiry" ++ show (snd cv)
41−
42− -- Configure 'ConnectInfo' to request temporary credentials on demand.
43− ci <- setSTSCredential assumeRole "https://play.min.io"
44− res <- runMinio ci $ do
45− buckets <- listBuckets
46− liftIO $ print $ "Top 5 buckets: " ++ show (take 5 buckets)
47− print res
examples/BucketExists.hs+7−4
@@ -16,17 +16,20 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21−import Control.Monad.IO.Class (liftIO)
23+import Control.Monad.IO.Class (liftIO)
22−import Network.Minio
23−import Prelude
24+import Prelude
24
25
25-- | The following example uses minio's play server at
26-- | The following example uses minio's play server at
26-- https://play.min.io. The endpoint and associated
27-- https://play.min.io. The endpoint and associated
27-- credentials are provided via the libary constant,
28-- credentials are provided via the libary constant,
28--
29--
29-- > minioPlayCI :: ConnectInfo
30-- > minioPlayCI :: ConnectInfo
31+--
32+
30main :: IO ()
33main :: IO ()
31main = do
34main = do
32 let bucket = "missingbucket"
35 let bucket = "missingbucket"
@@ -36,5 +39,5 @@ main = do
36 liftIO $ putStrLn $ "Does " ++ show bucket ++ " exist? - " ++ show foundBucket
39 liftIO $ putStrLn $ "Does " ++ show bucket ++ " exist? - " ++ show foundBucket
37
40
38 case res1 of
41 case res1 of
39− Left e -> putStrLn $ "bucketExists failed." ++ show e
42+ Left e -> putStrLn $ "bucketExists failed." ++ show e
40 Right () -> return ()
43 Right () -> return ()
examples/CopyObject.hs+14−12
@@ -16,40 +16,42 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21−import Network.Minio
22−import UnliftIO.Exception (catch, throwIO)
23+import UnliftIO.Exception (catch, throwIO)
23
24
24-- | The following example uses minio's play server at
25-- | The following example uses minio's play server at
25-- https://play.min.io. The endpoint and associated
26-- https://play.min.io. The endpoint and associated
26-- credentials are provided via the libary constant,
27-- credentials are provided via the libary constant,
27--
28--
28-- > minioPlayCI :: ConnectInfo
29-- > minioPlayCI :: ConnectInfo
30+--
31+
29main :: IO ()
32main :: IO ()
30main = do
33main = do
34+ let
31− let bucket = "test"
35+ bucket = "test"
32 object = "obj"
36 object = "obj"
33 objectCopy = "obj-copy"
37 objectCopy = "obj-copy"
34 localFile = "/etc/lsb-release"
38 localFile = "/etc/lsb-release"
35
39
36 res1 <- runMinio minioPlayCI $ do
40 res1 <- runMinio minioPlayCI $ do
37 -- 1. Make a bucket; Catch BucketAlreadyOwnedByYou exception.
41 -- 1. Make a bucket; Catch BucketAlreadyOwnedByYou exception.
38− catch
39− (makeBucket bucket Nothing)
42+ catch (makeBucket bucket Nothing) (
40− ( \e -> case e of
43+ \e -> case e of
41− BucketAlreadyOwnedByYou -> return ()
44+ BucketAlreadyOwnedByYou -> return ()
42− _ -> throwIO e
45+ _ -> throwIO e
43 )
46 )
44
47
45 -- 2. Upload a file to bucket/object.
48 -- 2. Upload a file to bucket/object.
46 fPutObject bucket object localFile defaultPutObjectOptions
49 fPutObject bucket object localFile defaultPutObjectOptions
47
50
48 -- 3. Copy bucket/object to bucket/objectCopy.
51 -- 3. Copy bucket/object to bucket/objectCopy.
49− copyObject
50− defaultDestinationInfo {dstBucket = bucket, dstObject = objectCopy}
52+ copyObject defaultDestinationInfo {dstBucket = bucket, dstObject = objectCopy}
51− defaultSourceInfo {srcBucket = bucket, srcObject = object}
53+ defaultSourceInfo { srcBucket = bucket , srcObject = object }
52
54
53 case res1 of
55 case res1 of
54− Left e -> putStrLn $ "copyObject failed." ++ show e
56+ Left e -> putStrLn $ "copyObject failed." ++ show e
55 Right () -> putStrLn "copyObject succeeded."
57 Right () -> putStrLn "copyObject succeeded."
examples/FileUploader.hs+24−23
@@ -16,39 +16,40 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
20+
19−{-# LANGUAGE OverloadedStrings #-}
21+{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE ScopedTypeVariables #-}
22{-# LANGUAGE ScopedTypeVariables #-}
23+import Network.Minio
21
24
25+import Data.Monoid ((<>))
22−import Data.Text (pack)
26+import Data.Text (pack)
23−import Network.Minio
24−import Options.Applicative
27+import Options.Applicative
25−import System.FilePath.Posix
28+import System.FilePath.Posix
26−import UnliftIO (throwIO, try)
29+import UnliftIO (throwIO, try)
30+
27−import Prelude
31+import Prelude
28
32
29-- | The following example uses minio's play server at
33-- | The following example uses minio's play server at
30-- https://play.min.io. The endpoint and associated
34-- https://play.min.io. The endpoint and associated
31-- credentials are provided via the libary constant,
35-- credentials are provided via the libary constant,
32--
36--
33-- > minioPlayCI :: ConnectInfo
37-- > minioPlayCI :: ConnectInfo
38+--
34
39
35-- optparse-applicative package based command-line parsing.
40-- optparse-applicative package based command-line parsing.
36fileNameArgs :: Parser FilePath
41fileNameArgs :: Parser FilePath
37−fileNameArgs =
42+fileNameArgs = strArgument
38− strArgument
39− ( metavar "FILENAME"
43+ (metavar "FILENAME"
40− <> help "Name of file to upload to AWS S3 or a MinIO server"
44+ <> help "Name of file to upload to AWS S3 or a MinIO server")
41− )
42
45
43cmdParser :: ParserInfo FilePath
46cmdParser :: ParserInfo FilePath
44−cmdParser =
47+cmdParser = info
45− info
46− (helper <*> fileNameArgs)
48+ (helper <*> fileNameArgs)
47− ( fullDesc
49+ (fullDesc
48− <> progDesc "FileUploader"
50+ <> progDesc "FileUploader"
49− <> header
51+ <> header
50− "FileUploader - a simple file-uploader program using minio-hs"
52+ "FileUploader - a simple file-uploader program using minio-hs")
51− )
52
53
53main :: IO ()
54main :: IO ()
54main = do
55main = do
@@ -63,12 +64,12 @@ main = do
63 bErr <- try $ makeBucket bucket Nothing
64 bErr <- try $ makeBucket bucket Nothing
64 case bErr of
65 case bErr of
65 Left BucketAlreadyOwnedByYou -> return ()
66 Left BucketAlreadyOwnedByYou -> return ()
66− Left e -> throwIO e
67+ Left e -> throwIO e
67− Right _ -> return ()
68+ Right _ -> return ()
68
69
69 -- Upload filepath to bucket; object is derived from filepath.
70 -- Upload filepath to bucket; object is derived from filepath.
70 fPutObject bucket object filepath defaultPutObjectOptions
71 fPutObject bucket object filepath defaultPutObjectOptions
71
72
72 case res of
73 case res of
73− Left e -> putStrLn $ "file upload failed due to " ++ show e
74+ Left e -> putStrLn $ "file upload failed due to " ++ (show e)
74 Right () -> putStrLn "file upload succeeded."
75 Right () -> putStrLn "file upload succeeded."
examples/GetConfig.hs+6−6
@@ -17,14 +17,14 @@
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19
19
20+{-# LANGUAGE OverloadedStrings #-}
20−import Network.Minio
21+import Network.Minio
21−import Network.Minio.AdminAPI
22+import Network.Minio.AdminAPI
23+
22−import Prelude
24+import Prelude
23
25
24main :: IO ()
26main :: IO ()
25main = do
27main = do
26− res <-
27− runMinio
28− minioPlayCI
28+ res <- runMinio minioPlayCI $
29 getConfig
29 getConfig
30 print res
30 print res
examples/GetObject.hs+10−5
@@ -16,26 +16,31 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21−import qualified Data.Conduit as C
23+import qualified Data.Conduit as C
22import qualified Data.Conduit.Binary as CB
24import qualified Data.Conduit.Binary as CB
23−import Network.Minio
25+
24−import Prelude
26+import Prelude
25
27
26-- | The following example uses minio's play server at
28-- | The following example uses minio's play server at
27-- https://play.min.io. The endpoint and associated
29-- https://play.min.io. The endpoint and associated
28-- credentials are provided via the libary constant,
30-- credentials are provided via the libary constant,
29--
31--
30-- > minioPlayCI :: ConnectInfo
32-- > minioPlayCI :: ConnectInfo
33+--
34+
31main :: IO ()
35main :: IO ()
32main = do
36main = do
37+ let
33− let bucket = "my-bucket"
38+ bucket = "my-bucket"
34 object = "my-object"
39 object = "my-object"
35 res <- runMinio minioPlayCI $ do
40 res <- runMinio minioPlayCI $ do
36 src <- getObject bucket object defaultGetObjectOptions
41 src <- getObject bucket object defaultGetObjectOptions
37 C.connect (gorObjectStream src) $ CB.sinkFileCautious "/tmp/my-object"
42 C.connect (gorObjectStream src) $ CB.sinkFileCautious "/tmp/my-object"
38
43
39 case res of
44 case res of
40− Left e -> putStrLn $ "getObject failed." ++ show e
45+ Left e -> putStrLn $ "getObject failed." ++ (show e)
41 Right _ -> putStrLn "getObject succeeded."
46 Right _ -> putStrLn "getObject succeeded."
examples/HeadObject.hs+11−8
@@ -16,25 +16,28 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
22+import Network.Minio.S3API
20
23
21−import Network.Minio
22−import Network.Minio.S3API
23−import Prelude
24+import Prelude
24
25
25-- | The following example uses minio's play server at
26-- | The following example uses minio's play server at
26-- https://play.min.io. The endpoint and associated
27-- https://play.min.io. The endpoint and associated
27-- credentials are provided via the libary constant,
28-- credentials are provided via the libary constant,
28--
29--
29-- > minioPlayCI :: ConnectInfo
30-- > minioPlayCI :: ConnectInfo
31+--
32+
30main :: IO ()
33main :: IO ()
31main = do
34main = do
35+ let
32− let bucket = "test"
36+ bucket = "test"
33 object = "passwd"
37 object = "passwd"
34− res <-
35− runMinio minioPlayCI $
38+ res <- runMinio minioPlayCI $
36− headObject bucket object []
39+ headObject bucket object []
37
40
38 case res of
41 case res of
39− Left e -> putStrLn $ "headObject failed." ++ show e
42+ Left e -> putStrLn $ "headObject failed." ++ show e
40 Right objInfo -> putStrLn $ "headObject succeeded." ++ show objInfo
43 Right objInfo -> putStrLn $ "headObject succeeded." ++ show objInfo
examples/Heal.hs+8−11
@@ -17,21 +17,18 @@
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19
19
20+{-# LANGUAGE OverloadedStrings #-}
20−import Network.Minio
21+import Network.Minio
21−import Network.Minio.AdminAPI
22+import Network.Minio.AdminAPI
23+
22−import Prelude
24+import Prelude
23
25
24main :: IO ()
26main :: IO ()
25main = do
27main = do
26 res <- runMinio minioPlayCI $
28 res <- runMinio minioPlayCI $
27 do
29 do
28− hsr <-
29− startHeal
30− Nothing
31− Nothing
32− HealOpts
33− { hoRecursive = True,
30+ hsr <- startHeal Nothing Nothing HealOpts { hoRecursive = True
34− hoDryRun = False
31+ , hoDryRun = False
35− }
32+ }
36 getHealStatus Nothing Nothing (hsrClientToken hsr)
33 getHealStatus Nothing Nothing (hsrClientToken hsr)
37 print res
34 print res
examples/ListBuckets.hs+5−3
@@ -16,17 +16,19 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21−import Control.Monad.IO.Class (liftIO)
23+import Control.Monad.IO.Class (liftIO)
22−import Network.Minio
23−import Prelude
24+import Prelude
24
25
25-- | The following example uses minio's play server at
26-- | The following example uses minio's play server at
26-- https://play.min.io. The endpoint and associated
27-- https://play.min.io. The endpoint and associated
27-- credentials are provided via the libary constant,
28-- credentials are provided via the libary constant,
28--
29--
29-- > minioPlayCI :: ConnectInfo
30-- > minioPlayCI :: ConnectInfo
31+--
30
32
31-- This example list buckets that belongs to the user and returns
33-- This example list buckets that belongs to the user and returns
32-- region of the first bucket returned.
34-- region of the first bucket returned.
examples/ListIncompleteUploads.hs+19−17
@@ -16,36 +16,38 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21−import Conduit
23+import Conduit
22−import Network.Minio
23−import Prelude
24+import Prelude
24
25
25-- | The following example uses minio's play server at
26-- | The following example uses minio's play server at
26-- https://play.min.io. The endpoint and associated
27-- https://play.min.io. The endpoint and associated
27-- credentials are provided via the libary constant,
28-- credentials are provided via the libary constant,
28--
29--
29-- > minioPlayCI :: ConnectInfo
30-- > minioPlayCI :: ConnectInfo
31+--
32+
30main :: IO ()
33main :: IO ()
31main = do
34main = do
35+ let
32− let bucket = "test"
36+ bucket = "test"
33
37
34 -- Performs a recursive listing of incomplete uploads under bucket "test"
38 -- Performs a recursive listing of incomplete uploads under bucket "test"
35 -- on a local minio server.
39 -- on a local minio server.
36− res <-
37− runMinio minioPlayCI $
40+ res <- runMinio minioPlayCI $
38− runConduit $
39− listIncompleteUploads bucket Nothing True .| mapM_C (liftIO . print)
41+ runConduit $ listIncompleteUploads bucket Nothing True .| mapM_C (\v -> (liftIO $ print v))
40 print res
42 print res
41
43
42−{-
44+ {-
43− Following is the output of the above program on a local MinIO server.
45+ Following is the output of the above program on a local MinIO server.
44
46
45− Right [UploadInfo { uiKey = "go1.6.2.linux-amd64.tar.gz"
47+ Right [UploadInfo { uiKey = "go1.6.2.linux-amd64.tar.gz"
46− , uiUploadId = "063eb592-bdd7-4a0c-be48-34fb3ceb63e2"
48+ , uiUploadId = "063eb592-bdd7-4a0c-be48-34fb3ceb63e2"
47− , uiInitTime = 2017-03-01 10:16:25.698 UTC
49+ , uiInitTime = 2017-03-01 10:16:25.698 UTC
48− , uiSize = 17731794
50+ , uiSize = 17731794
49− }
51+ }
50− ]
52+ ]
51−-}
53+ -}
examples/ListObjects.hs+15−13
@@ -16,31 +16,33 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
22+
23+import Conduit
24+import Prelude
20
25
21−import Conduit
22−import Network.Minio
23−import Prelude
24
26
25-- | The following example uses minio's play server at
27-- | The following example uses minio's play server at
26-- https://play.min.io. The endpoint and associated
28-- https://play.min.io. The endpoint and associated
27-- credentials are provided via the libary constant,
29-- credentials are provided via the libary constant,
28--
30--
29-- > minioPlayCI :: ConnectInfo
31-- > minioPlayCI :: ConnectInfo
32+--
33+
30main :: IO ()
34main :: IO ()
31main = do
35main = do
36+ let
32− let bucket = "test"
37+ bucket = "test"
33
38
34 -- Performs a recursive listing of all objects under bucket "test"
39 -- Performs a recursive listing of all objects under bucket "test"
35 -- on play.min.io.
40 -- on play.min.io.
36− res <-
37− runMinio minioPlayCI $
41+ res <- runMinio minioPlayCI $
38− runConduit $
39− listObjects bucket Nothing True .| mapM_C (liftIO . print)
42+ runConduit $ listObjects bucket Nothing True .| mapM_C (\v -> (liftIO $ print v))
40 print res
43 print res
44+ {-
45+ Following is the output of the above program on a local MinIO server.
41
46
42−{-
43− Following is the output of the above program on a local MinIO server.
44−
45− Right [ObjectInfo {oiObject = "ADVANCED.png", oiModTime = 2017-02-10 05:33:24.816 UTC, oiETag = "\"a69f3af6bbb06fe1d42ac910ec30482f\"", oiSize = 94026},ObjectInfo {oiObject = "obj", oiModTime = 2017-02-10 08:49:26.777 UTC, oiETag = "\"715a872a253a3596652c1490081b4b6a-1\"", oiSize = 15728640}]
47+ Right [ObjectInfo {oiObject = "ADVANCED.png", oiModTime = 2017-02-10 05:33:24.816 UTC, oiETag = "\"a69f3af6bbb06fe1d42ac910ec30482f\"", oiSize = 94026},ObjectInfo {oiObject = "obj", oiModTime = 2017-02-10 08:49:26.777 UTC, oiETag = "\"715a872a253a3596652c1490081b4b6a-1\"", oiSize = 15728640}]
46−-}
48+ -}
examples/MakeBucket.hs+9−6
@@ -16,21 +16,24 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
20+
19{-# LANGUAGE OverloadedStrings #-}
21{-# LANGUAGE OverloadedStrings #-}
22+import Network.Minio
20
23
21−import Network.Minio
22−import Prelude
24+import Prelude
23
25
24-- | The following example uses minio's play server at
26-- | The following example uses minio's play server at
25-- https://play.min.io. The endpoint and associated
27-- https://play.min.io. The endpoint and associated
26-- credentials are provided via the libary constant,
28-- credentials are provided via the libary constant,
27--
29--
28-- > minioPlayCI :: ConnectInfo
30-- > minioPlayCI :: ConnectInfo
31+--
32+
29main :: IO ()
33main :: IO ()
30main = do
34main = do
31 let bucket = "my-bucket"
35 let bucket = "my-bucket"
32− res <-
33− runMinio minioPlayCI $
36+ res <- runMinio minioPlayCI $
34− -- N B the region provided for makeBucket is optional.
37+ -- N B the region provided for makeBucket is optional.
35− makeBucket bucket (Just "us-east-1")
38+ makeBucket bucket (Just "us-east-1")
36 print res
39 print res
examples/PresignedGetObject.hs+28−32
@@ -16,37 +16,39 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21−import Control.Monad.IO.Class (liftIO)
23+import Control.Monad.IO.Class (liftIO)
22−import qualified Data.ByteString.Char8 as B
24+import qualified Data.ByteString.Char8 as B
23−import Data.CaseInsensitive (original)
25+import Data.CaseInsensitive (original)
24import qualified Data.Conduit.Combinators as CC
26import qualified Data.Conduit.Combinators as CC
25−import qualified Data.Text.Encoding as E
27+import qualified Data.Text.Encoding as E
26−import Network.Minio
27
28
28-- | The following example uses minio's play server at
29-- | The following example uses minio's play server at
29-- https://play.min.io. The endpoint and associated
30-- https://play.min.io. The endpoint and associated
30-- credentials are provided via the libary constant,
31-- credentials are provided via the libary constant,
31--
32--
32-- > minioPlayCI :: ConnectInfo
33-- > minioPlayCI :: ConnectInfo
34+--
35+
33main :: IO ()
36main :: IO ()
34main = do
37main = do
38+ let
35− let bucket = "my-bucket"
39+ bucket = "my-bucket"
36− object = "my-object"
40+ object = "my-object"
37− kb15 = 15 * 1024
41+ kb15 = 15*1024
42+
38− -- Set query parameter to modify content disposition response
43+ -- Set query parameter to modify content disposition response
39− -- header
44+ -- header
40− queryParam =
41− [ ( "response-content-disposition",
45+ queryParam = [("response-content-disposition",
42− Just "attachment; filename=\"your-filename.txt\""
46+ Just "attachment; filename=\"your-filename.txt\"")]
43− )
44− ]
45
47
46 res <- runMinio minioPlayCI $ do
48 res <- runMinio minioPlayCI $ do
47 liftIO $ B.putStrLn "Upload a file that we will fetch with a presigned URL..."
49 liftIO $ B.putStrLn "Upload a file that we will fetch with a presigned URL..."
48 putObject bucket object (CC.repeat "a") (Just kb15) defaultPutObjectOptions
50 putObject bucket object (CC.repeat "a") (Just kb15) defaultPutObjectOptions
49− liftIO $ putStrLn "Done. Object created at: my-bucket/my-object"
51+ liftIO $ putStrLn $ "Done. Object created at: my-bucket/my-object"
50
52
51 -- Extract Etag of uploaded object
53 -- Extract Etag of uploaded object
52 oi <- statObject bucket object defaultGetObjectOptions
54 oi <- statObject bucket object defaultGetObjectOptions
@@ -59,29 +61,23 @@ main = do
59 -- Generate a URL with 7 days expiry time - note that the headers
61 -- Generate a URL with 7 days expiry time - note that the headers
60 -- used above must be added to the request with the signed URL
62 -- used above must be added to the request with the signed URL
61 -- generated.
63 -- generated.
62− url <-
63− presignedGetObjectUrl
64+ url <- presignedGetObjectUrl "my-bucket" "my-object" (7*24*3600)
64− "my-bucket"
65− "my-object"
66− (7 * 24 * 3600)
67− queryParam
65+ queryParam headers
68− headers
69
66
70 return (headers, etag, url)
67 return (headers, etag, url)
71
68
72 case res of
69 case res of
73 Left e -> putStrLn $ "presignedPutObject URL failed." ++ show e
70 Left e -> putStrLn $ "presignedPutObject URL failed." ++ show e
74 Right (headers, _, url) -> do
71 Right (headers, _, url) -> do
72+
75 -- We generate a curl command to demonstrate usage of the signed
73 -- We generate a curl command to demonstrate usage of the signed
76 -- URL.
74 -- URL.
75+ let
77− let hdrOpt (k, v) = B.concat ["-H '", original k, ": ", v, "'"]
76+ hdrOpt (k, v) = B.concat ["-H '", original k, ": ", v, "'"]
78− curlCmd =
79− B.intercalate " " $
77+ curlCmd = B.intercalate " " $
80− ["curl --fail"]
81− ++ map hdrOpt headers
78+ ["curl --fail"] ++ map hdrOpt headers ++
82− ++ ["-o /tmp/myfile", B.concat ["'", url, "'"]]
79+ ["-o /tmp/myfile", B.concat ["'", url, "'"]]
83
80
84− putStrLn $
85− "The following curl command would use the presigned "
81+ putStrLn $ "The following curl command would use the presigned " ++
86− ++ "URL to fetch the object and write it to \"/tmp/myfile\":"
82+ "URL to fetch the object and write it to \"/tmp/myfile\":"
87 B.putStrLn curlCmd
83 B.putStrLn curlCmd
examples/PresignedPostPolicy.hs+39−42
@@ -16,72 +16,69 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21−import qualified Data.ByteString as B
23+import qualified Data.ByteString as B
22import qualified Data.ByteString.Char8 as Char8
24import qualified Data.ByteString.Char8 as Char8
23−import qualified Data.HashMap.Strict as H
25+import qualified Data.HashMap.Strict as H
24−import qualified Data.Text.Encoding as Enc
26+import qualified Data.Text.Encoding as Enc
25−import qualified Data.Time as Time
27+import qualified Data.Time as Time
26−import Network.Minio
27
28
28-- | The following example uses minio's play server at
29-- | The following example uses minio's play server at
29-- https://play.min.io. The endpoint and associated
30-- https://play.min.io. The endpoint and associated
30-- credentials are provided via the libary constant,
31-- credentials are provided via the libary constant,
31--
32--
32-- > minioPlayCI :: ConnectInfo
33-- > minioPlayCI :: ConnectInfo
34+--
35+
33main :: IO ()
36main :: IO ()
34main = do
37main = do
35 now <- Time.getCurrentTime
38 now <- Time.getCurrentTime
39+ let
36− let bucket = "my-bucket"
40+ bucket = "my-bucket"
37− object = "photos/my-object"
41+ object = "photos/my-object"
42+
38− -- set an expiration time of 10 days
43+ -- set an expiration time of 10 days
39− expireTime = Time.addUTCTime (3600 * 24 * 10) now
44+ expireTime = Time.addUTCTime (3600 * 24 * 10) now
45+
40− -- create a policy with expiration time and conditions - since the
46+ -- create a policy with expiration time and conditions - since the
41− -- conditions are validated, newPostPolicy returns an Either value
47+ -- conditions are validated, newPostPolicy returns an Either value
42− policyE =
43− newPostPolicy
48+ policyE = newPostPolicy expireTime
44− expireTime
45− [ -- set the object name condition
49+ [ -- set the object name condition
46− ppCondKey object,
50+ ppCondKey object
47− -- set the bucket name condition
51+ -- set the bucket name condition
48− ppCondBucket bucket,
52+ , ppCondBucket bucket
49− -- set the size range of object as 1B to 10MiB
53+ -- set the size range of object as 1B to 10MiB
50− ppCondContentLengthRange 1 (10 * 1024 * 1024),
54+ , ppCondContentLengthRange 1 (10*1024*1024)
51− -- set content type as jpg image
55+ -- set content type as jpg image
52− ppCondContentType "image/jpeg",
56+ , ppCondContentType "image/jpeg"
53− -- on success set the server response code to 200
57+ -- on success set the server response code to 200
54− ppCondSuccessActionStatus 200
58+ , ppCondSuccessActionStatus 200
55− ]
59+ ]
56
60
57 case policyE of
61 case policyE of
58− Left err -> print err
62+ Left err -> putStrLn $ show err
59 Right policy -> do
63 Right policy -> do
60 res <- runMinio minioPlayCI $ do
64 res <- runMinio minioPlayCI $ do
61 (url, formData) <- presignedPostPolicy policy
65 (url, formData) <- presignedPostPolicy policy
62
66
63 -- a curl command is output to demonstrate using the generated
67 -- a curl command is output to demonstrate using the generated
64 -- URL and form-data
68 -- URL and form-data
65− let formFn (k, v) =
69+ let
66− B.concat
67− [ "-F ",
68− Enc.encodeUtf8 k,
70+ formFn (k, v) = B.concat ["-F ", Enc.encodeUtf8 k, "=",
69− "=",
70− "'",
71− v,
71+ "'", v, "'"]
72− "'"
73− ]
74− formOptions = B.intercalate " " $ map formFn $ H.toList formData
72+ formOptions = B.intercalate " " $ map formFn $ H.toList formData
73+
75
74
76− return $
77− B.intercalate
75+ return $ B.intercalate " " $
78− " "
79− ["curl", formOptions, "-F file=@/tmp/photo.jpg", url]
76+ ["curl", formOptions, "-F file=@/tmp/photo.jpg", url]
80
77
81 case res of
78 case res of
82− Left e -> putStrLn $ "post-policy error: " ++ show e
79+ Left e -> putStrLn $ "post-policy error: " ++ (show e)
83 Right cmd -> do
80 Right cmd -> do
84− putStrLn "Put a photo at /tmp/photo.jpg and run command:\n"
81+ putStrLn $ "Put a photo at /tmp/photo.jpg and run command:\n"
85
82
86 -- print the generated curl command
83 -- print the generated curl command
87 Char8.putStrLn cmd
84 Char8.putStrLn cmd
examples/PresignedPutObject.hs+21−20
@@ -16,43 +16,44 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21import qualified Data.ByteString.Char8 as B
23import qualified Data.ByteString.Char8 as B
22−import Data.CaseInsensitive (original)
24+import Data.CaseInsensitive (original)
23−import Network.Minio
24
25
25-- | The following example uses minio's play server at
26-- | The following example uses minio's play server at
26-- https://play.min.io. The endpoint and associated
27-- https://play.min.io. The endpoint and associated
27-- credentials are provided via the libary constant,
28-- credentials are provided via the libary constant,
28--
29--
29-- > minioPlayCI :: ConnectInfo
30-- > minioPlayCI :: ConnectInfo
31+--
32+
30main :: IO ()
33main :: IO ()
31main = do
34main = do
35+ let
32− let -- Use headers to set user-metadata - note that this header will
36+ -- Use headers to set user-metadata - note that this header will
33− -- need to be set when the URL is used to make an upload.
37+ -- need to be set when the URL is used to make an upload.
34− headers =
35− [ ( "x-amz-meta-url-creator",
38+ headers = [("x-amz-meta-url-creator",
36− "minio-hs-presigned-put-example"
39+ "minio-hs-presigned-put-example")]
37− )
38− ]
39 res <- runMinio minioPlayCI $ do
40 res <- runMinio minioPlayCI $ do
41+
40 -- generate a URL with 7 days expiry time
42 -- generate a URL with 7 days expiry time
41− presignedPutObjectUrl "my-bucket" "my-object" (7 * 24 * 3600) headers
43+ presignedPutObjectUrl "my-bucket" "my-object" (7*24*3600) headers
42
44
43 case res of
45 case res of
44 Left e -> putStrLn $ "presignedPutObject URL failed." ++ show e
46 Left e -> putStrLn $ "presignedPutObject URL failed." ++ show e
45 Right url -> do
47 Right url -> do
48+
46 -- We generate a curl command to demonstrate usage of the signed
49 -- We generate a curl command to demonstrate usage of the signed
47 -- URL.
50 -- URL.
51+ let
48− let hdrOpt (k, v) = B.concat ["-H '", original k, ": ", v, "'"]
52+ hdrOpt (k, v) = B.concat ["-H '", original k, ": ", v, "'"]
49− curlCmd =
50− B.intercalate " " $
53+ curlCmd = B.intercalate " " $
51− ["curl "]
52− ++ map hdrOpt headers
54+ ["curl "] ++ map hdrOpt headers ++
53− ++ ["-T /tmp/myfile", B.concat ["'", url, "'"]]
55+ ["-T /tmp/myfile", B.concat ["'", url, "'"]]
54−
56+
55− putStrLn $
56− "The following curl command would use the presigned "
57+ putStrLn $ "The following curl command would use the presigned " ++
57− ++ "URL to upload the file at \"/tmp/myfile\":"
58+ "URL to upload the file at \"/tmp/myfile\":"
58 B.putStrLn curlCmd
59 B.putStrLn curlCmd
examples/PutObject.hs+14−11
@@ -16,36 +16,39 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21import qualified Data.Conduit.Combinators as CC
23import qualified Data.Conduit.Combinators as CC
22−import Network.Minio
24+
23−import Prelude
25+import Prelude
24
26
25-- | The following example uses minio's play server at
27-- | The following example uses minio's play server at
26-- https://play.min.io. The endpoint and associated
28-- https://play.min.io. The endpoint and associated
27-- credentials are provided via the libary constant,
29-- credentials are provided via the libary constant,
28--
30--
29-- > minioPlayCI :: ConnectInfo
31-- > minioPlayCI :: ConnectInfo
32+--
33+
30main :: IO ()
34main :: IO ()
31main = do
35main = do
36+ let
32− let bucket = "test"
37+ bucket = "test"
33 object = "obj"
38 object = "obj"
34 localFile = "/etc/lsb-release"
39 localFile = "/etc/lsb-release"
35 kb15 = 15 * 1024
40 kb15 = 15 * 1024
36
41
37 -- Eg 1. Upload a stream of repeating "a" using putObject with default options.
42 -- Eg 1. Upload a stream of repeating "a" using putObject with default options.
38− res1 <-
39− runMinio minioPlayCI $
43+ res1 <- runMinio minioPlayCI $
40− putObject bucket object (CC.repeat "a") (Just kb15) defaultPutObjectOptions
44+ putObject bucket object (CC.repeat "a") (Just kb15) defaultPutObjectOptions
41 case res1 of
45 case res1 of
42− Left e -> putStrLn $ "putObject failed." ++ show e
46+ Left e -> putStrLn $ "putObject failed." ++ show e
43 Right () -> putStrLn "putObject succeeded."
47 Right () -> putStrLn "putObject succeeded."
44
48
45 -- Eg 2. Upload a file using fPutObject with default options.
49 -- Eg 2. Upload a file using fPutObject with default options.
46− res2 <-
47− runMinio minioPlayCI $
50+ res2 <- runMinio minioPlayCI $
48− fPutObject bucket object localFile defaultPutObjectOptions
51+ fPutObject bucket object localFile defaultPutObjectOptions
49 case res2 of
52 case res2 of
50− Left e -> putStrLn $ "fPutObject failed." ++ show e
53+ Left e -> putStrLn $ "fPutObject failed." ++ show e
51 Right () -> putStrLn "fPutObject succeeded."
54 Right () -> putStrLn "fPutObject succeeded."
examples/RemoveBucket.hs+8−3
@@ -16,18 +16,23 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
20+
19{-# LANGUAGE OverloadedStrings #-}
21{-# LANGUAGE OverloadedStrings #-}
22+import Network.Minio
20
23
21−import Network.Minio
22−import Prelude
24+import Prelude
23
25
24-- | The following example uses minio's play server at
26-- | The following example uses minio's play server at
25-- https://play.min.io. The endpoint and associated
27-- https://play.min.io. The endpoint and associated
26-- credentials are provided via the libary constant,
28-- credentials are provided via the libary constant,
27--
29--
28-- > minioPlayCI :: ConnectInfo
30-- > minioPlayCI :: ConnectInfo
31+--
32+
29main :: IO ()
33main :: IO ()
30main = do
34main = do
35+ let
31− let bucket = "my-bucket"
36+ bucket = "my-bucket"
32 res <- runMinio minioPlayCI $ removeBucket bucket
37 res <- runMinio minioPlayCI $ removeBucket bucket
33 print res
38 print res
examples/RemoveIncompleteUpload.hs+10−7
@@ -16,24 +16,27 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
20
22
21−import Network.Minio
22−import Prelude
23+import Prelude
23
24
24-- | The following example uses minio's play server at
25-- | The following example uses minio's play server at
25-- https://play.min.io. The endpoint and associated
26-- https://play.min.io. The endpoint and associated
26-- credentials are provided via the libary constant,
27-- credentials are provided via the libary constant,
27--
28--
28-- > minioPlayCI :: ConnectInfo
29-- > minioPlayCI :: ConnectInfo
30+--
31+
29main :: IO ()
32main :: IO ()
30main = do
33main = do
34+ let
31− let bucket = "mybucket"
35+ bucket = "mybucket"
32− object = "myobject"
36+ object = "myobject"
33
37
34− res <-
35− runMinio minioPlayCI $
38+ res <- runMinio minioPlayCI $
36− removeIncompleteUpload bucket object
39+ removeIncompleteUpload bucket object
37
40
38 case res of
41 case res of
39 Left _ -> putStrLn $ "Failed to remove " ++ show bucket ++ "/" ++ show object
42 Left _ -> putStrLn $ "Failed to remove " ++ show bucket ++ "/" ++ show object
examples/RemoveObject.hs+8−7
@@ -16,19 +16,20 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
20
21
21−import Network.Minio
22+import Network.Minio
22−import Prelude
23+import Prelude
23
24
24main :: IO ()
25main :: IO ()
25main = do
26main = do
27+ let
26− let bucket = "mybucket"
28+ bucket = "mybucket"
27− object = "myobject"
29+ object = "myobject"
28
30
29− res <-
30− runMinio minioPlayCI $
31+ res <- runMinio minioPlayCI $
31− removeObject bucket object
32+ removeObject bucket object
32
33
33 case res of
34 case res of
34 Left _ -> putStrLn $ "Failed to remove " ++ show bucket ++ "/" ++ show object
35 Left _ -> putStrLn $ "Failed to remove " ++ show bucket ++ "/" ++ show object
examples/SelectObject.hs+26−24
@@ -16,32 +16,34 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
22+
23+import qualified Conduit as C
24+import Control.Monad (when)
20
25
21−import qualified Conduit as C
22−import Control.Monad (unless)
23−import Network.Minio
24−import Prelude
26+import Prelude
25
27
26main :: IO ()
28main :: IO ()
27main = do
29main = do
28− let bucket = "selectbucket"
30+ let bucket = "selectbucket"
29− object = "1.csv"
31+ object = "1.csv"
30− content =
31− "Name,Place,Temperature\n"
32+ content = "Name,Place,Temperature\n"
32− <> "James,San Jose,76\n"
33+ <> "James,San Jose,76\n"
33− <> "Alicia,San Leandro,88\n"
34+ <> "Alicia,San Leandro,88\n"
34− <> "Mark,San Carlos,90\n"
35+ <> "Mark,San Carlos,90\n"
35−
36+
36− res <- runMinio minioPlayCI $ do
37+ res <- runMinio minioPlayCI $ do
38+
37− exists <- bucketExists bucket
39+ exists <- bucketExists bucket
38− unless exists $
40+ when (not exists) $
39− makeBucket bucket Nothing
41+ makeBucket bucket Nothing
40−
42+
41− C.liftIO $ putStrLn "Uploading csv object"
43+ C.liftIO $ putStrLn "Uploading csv object"
42− putObject bucket object (C.sourceLazy content) Nothing defaultPutObjectOptions
44+ putObject bucket object (C.sourceLazy content) Nothing defaultPutObjectOptions
43−
45+
44− let sr = selectRequest "Select * from s3object" defaultCsvInput defaultCsvOutput
46+ let sr = selectRequest "Select * from s3object" defaultCsvInput defaultCsvOutput
45− res <- selectObjectContent bucket object sr
47+ res <- selectObjectContent bucket object sr
46− C.runConduit $ res C..| getPayloadBytes C..| C.stdoutC
48+ C.runConduit $ res C..| getPayloadBytes C..| C.stdoutC
47− print res
49+ print res
examples/ServerInfo.hs+7−7
@@ -17,14 +17,14 @@
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19
19
20+{-# LANGUAGE OverloadedStrings #-}
20−import Network.Minio
21+import Network.Minio
21−import Network.Minio.AdminAPI
22+import Network.Minio.AdminAPI
23+
22−import Prelude
24+import Prelude
23
25
24main :: IO ()
26main :: IO ()
25main = do
27main = do
26− res <-
27− runMinio
28− minioPlayCI
28+ res <- runMinio minioPlayCI $
29− getServerInfo
29+ getServerInfo
30 print res
30 print res
examples/ServiceSendRestart.hs+7−6
@@ -17,13 +17,14 @@
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19
19
20+{-# LANGUAGE OverloadedStrings #-}
20−import Network.Minio
21+import Network.Minio
21−import Network.Minio.AdminAPI
22+import Network.Minio.AdminAPI
23+
22−import Prelude
24+import Prelude
23
25
24main :: IO ()
26main :: IO ()
25main = do
27main = do
26− res <-
27− runMinio minioPlayCI $
28+ res <- runMinio minioPlayCI $
28− serviceSendAction ServiceActionRestart
29+ serviceSendAction ServiceActionRestart
29 print res
30 print res
examples/ServiceSendStop.hs+7−6
@@ -17,13 +17,14 @@
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19
19
20+{-# LANGUAGE OverloadedStrings #-}
20−import Network.Minio
21+import Network.Minio
21−import Network.Minio.AdminAPI
22+import Network.Minio.AdminAPI
23+
22−import Prelude
24+import Prelude
23
25
24main :: IO ()
26main :: IO ()
25main = do
27main = do
26− res <-
27− runMinio minioPlayCI $
28+ res <- runMinio minioPlayCI $
28− serviceSendAction ServiceActionStop
29+ serviceSendAction ServiceActionStop
29 print res
30 print res
examples/ServiceStatus.hs+7−7
@@ -17,14 +17,14 @@
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19
19
20+{-# LANGUAGE OverloadedStrings #-}
20−import Network.Minio
21+import Network.Minio
21−import Network.Minio.AdminAPI
22+import Network.Minio.AdminAPI
23+
22−import Prelude
24+import Prelude
23
25
24main :: IO ()
26main :: IO ()
25main = do
27main = do
26− res <-
27− runMinio
28− minioPlayCI
28+ res <- runMinio minioPlayCI $
29− serviceStatus
29+ serviceStatus
30 print res
30 print res
examples/SetConfig.hs+4−3
@@ -16,11 +16,12 @@
16-- See the License for the specific language governing permissions and
16-- See the License for the specific language governing permissions and
17-- limitations under the License.
17-- limitations under the License.
18--
18--
19+
19{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21+import Network.Minio
22+import Network.Minio.AdminAPI
20
23
21−import Network.Minio
22−import Network.Minio.AdminAPI
23−import Prelude
24+import Prelude
24
25
25main :: IO ()
26main :: IO ()
26main = do
27main = do
minio-hs.cabal+17−73
@@ -1,6 +1,6 @@
1−cabal-version: 2.4
1+cabal-version: 2.2
2name: minio-hs
2name: minio-hs
3−version: 1.7.0
3+version: 1.5.2
4synopsis: A MinIO Haskell Library for Amazon S3 compatible cloud
4synopsis: A MinIO Haskell Library for Amazon S3 compatible cloud
5 storage.
5 storage.
6description: The MinIO Haskell client library provides simple APIs to
6description: The MinIO Haskell client library provides simple APIs to
@@ -14,70 +14,29 @@ maintainer: dev@min.io
14category: Network, AWS, Object Storage
14category: Network, AWS, Object Storage
15build-type: Simple
15build-type: Simple
16stability: Experimental
16stability: Experimental
17−extra-doc-files:
17+extra-source-files:
18 CHANGELOG.md
18 CHANGELOG.md
19 CONTRIBUTING.md
19 CONTRIBUTING.md
20 docs/API.md
20 docs/API.md
21− README.md
22−extra-source-files:
23 examples/*.hs
21 examples/*.hs
22+ README.md
24 stack.yaml
23 stack.yaml
25−tested-with: GHC == 8.10.7
26− , GHC == 9.0.2
27− , GHC == 9.2.8
28− , GHC == 9.4.8
29− , GHC == 9.6.5
30− , GHC == 9.8.2
31
24
32−source-repository head
33− type: git
34− location: https://github.com/minio/minio-hs.git
35−
36−Flag dev
37− Description: Build package in development mode
38− Default: False
39− Manual: True
40
25
41common base-settings
26common base-settings
42 ghc-options: -Wall
27 ghc-options: -Wall
43− -Wcompat
44− -Widentities
45− -Wincomplete-uni-patterns
46− -Wincomplete-record-updates
47− -haddock
48− if impl(ghc >= 8.0)
49− ghc-options: -Wredundant-constraints
50− if impl(ghc >= 8.2)
51− ghc-options: -fhide-source-paths
52− if impl(ghc >= 8.4)
53− ghc-options: -Wpartial-fields
54− -- -Wmissing-export-lists
55− if impl(ghc >= 8.8)
56− ghc-options: -Wmissing-deriving-strategies
57− -Werror=missing-deriving-strategies
58− -- if impl(ghc >= 8.10)
59− -- ghc-options: -Wunused-packages -- disabled due to bug related to mixin config
60− if impl(ghc >= 9.0)
61− ghc-options: -Winvalid-haddock
62− if impl(ghc >= 9.2)
63− ghc-options: -Wredundant-bang-patterns
64− if flag(dev)
65− ghc-options: -Werror
66−
67 default-language: Haskell2010
28 default-language: Haskell2010
68−
69 default-extensions: BangPatterns
29 default-extensions: BangPatterns
70− , DerivingStrategies
71 , FlexibleContexts
30 , FlexibleContexts
72 , FlexibleInstances
31 , FlexibleInstances
73− , LambdaCase
74 , MultiParamTypeClasses
32 , MultiParamTypeClasses
75 , MultiWayIf
33 , MultiWayIf
34+ , NoImplicitPrelude
76 , OverloadedStrings
35 , OverloadedStrings
77 , RankNTypes
36 , RankNTypes
78 , ScopedTypeVariables
37 , ScopedTypeVariables
38+ , TypeFamilies
79 , TupleSections
39 , TupleSections
80−
81 other-modules: Lib.Prelude
40 other-modules: Lib.Prelude
82 , Network.Minio.API
41 , Network.Minio.API
83 , Network.Minio.APICommon
42 , Network.Minio.APICommon
@@ -95,31 +54,22 @@ common base-settings
95 , Network.Minio.Utils
54 , Network.Minio.Utils
96 , Network.Minio.XmlGenerator
55 , Network.Minio.XmlGenerator
97 , Network.Minio.XmlParser
56 , Network.Minio.XmlParser
98− , Network.Minio.XmlCommon
99 , Network.Minio.JsonParser
57 , Network.Minio.JsonParser
100− , Network.Minio.Credentials.Types
101− , Network.Minio.Credentials.AssumeRole
102− , Network.Minio.Credentials
103−
104− mixins: base hiding (Prelude)
105− , relude (Relude as Prelude)
106− , relude
107−
108 build-depends: base >= 4.7 && < 5
58 build-depends: base >= 4.7 && < 5
109− , relude >= 0.7 && < 2
59+ , protolude >= 0.2 && < 0.3
110− , aeson >= 1.2 && < 3
60+ , aeson >= 1.2
111 , base64-bytestring >= 1.0
61 , base64-bytestring >= 1.0
112 , binary >= 0.8.5.0
62 , binary >= 0.8.5.0
113 , bytestring >= 0.10
63 , bytestring >= 0.10
114 , case-insensitive >= 1.2
64 , case-insensitive >= 1.2
115 , conduit >= 1.3
65 , conduit >= 1.3
116 , conduit-extra >= 1.3
66 , conduit-extra >= 1.3
117− , crypton-connection
67+ , connection
118 , cryptonite >= 0.25
68 , cryptonite >= 0.25
119 , cryptonite-conduit >= 0.2
69 , cryptonite-conduit >= 0.2
120− , data-default-class
121 , digest >= 0.0.1
70 , digest >= 0.0.1
122 , directory
71 , directory
72+ , exceptions
123 , filepath >= 1.4
73 , filepath >= 1.4
124 , http-client >= 0.5
74 , http-client >= 0.5
125 , http-client-tls
75 , http-client-tls
@@ -127,15 +77,14 @@ common base-settings
127 , http-types >= 0.12
77 , http-types >= 0.12
128 , ini
78 , ini
129 , memory >= 0.14
79 , memory >= 0.14
130− , network-uri
80+ , raw-strings-qq >= 1
131 , resourcet >= 1.2
81 , resourcet >= 1.2
132 , retry
82 , retry
133 , text >= 1.2
83 , text >= 1.2
134− , time >= 1.9
84+ , time >= 1.8
135− , time-units ^>= 1.0.0
136 , transformers >= 0.5
85 , transformers >= 0.5
137− , unliftio >= 0.2 && < 0.3
86+ , unliftio >= 0.2
138− , unliftio-core >= 0.2 && < 0.3
87+ , unliftio-core >= 0.1 && < 0.2
139 , unordered-containers >= 0.2
88 , unordered-containers >= 0.2
140 , xml-conduit >= 1.8
89 , xml-conduit >= 1.8
141
90
@@ -166,9 +115,7 @@ test-suite minio-hs-live-server-test
166 , Network.Minio.Utils.Test
115 , Network.Minio.Utils.Test
167 , Network.Minio.XmlGenerator.Test
116 , Network.Minio.XmlGenerator.Test
168 , Network.Minio.XmlParser.Test
117 , Network.Minio.XmlParser.Test
169− , Network.Minio.Credentials
170 build-depends: minio-hs
118 build-depends: minio-hs
171− , raw-strings-qq
172 , tasty
119 , tasty
173 , tasty-hunit
120 , tasty-hunit
174 , tasty-quickcheck
121 , tasty-quickcheck
@@ -183,7 +130,6 @@ test-suite minio-hs-test
183 hs-source-dirs: test, src
130 hs-source-dirs: test, src
184 main-is: Spec.hs
131 main-is: Spec.hs
185 build-depends: minio-hs
132 build-depends: minio-hs
186− , raw-strings-qq
187 , QuickCheck
133 , QuickCheck
188 , tasty
134 , tasty
189 , tasty-hunit
135 , tasty-hunit
@@ -200,7 +146,6 @@ test-suite minio-hs-test
200 , Network.Minio.Utils.Test
146 , Network.Minio.Utils.Test
201 , Network.Minio.XmlGenerator.Test
147 , Network.Minio.XmlGenerator.Test
202 , Network.Minio.XmlParser.Test
148 , Network.Minio.XmlParser.Test
203− , Network.Minio.Credentials
204
149
205Flag examples
150Flag examples
206 Description: Build the examples
151 Description: Build the examples
@@ -347,7 +292,6 @@ executable SetConfig
347 scope: private
292 scope: private
348 main-is: SetConfig.hs
293 main-is: SetConfig.hs
349
294
350−executable AssumeRole
295+source-repository head
351− import: examples-settings
296+ type: git
352− scope: private
297+ location: https://github.com/minio/minio-hs
353− main-is: AssumeRole.hs
src/Lib/Prelude.hs+11−33
@@ -15,41 +15,19 @@
15--
15--
16
16
17module Lib.Prelude
17module Lib.Prelude
18− ( module Exports,
18+ ( module Exports
19− both,
19+ , both
20− showBS,
21− toStrictBS,
22− fromStrictBS,
23− lastMay,
24− )
25−where
20+ ) where
26
21
22+import Protolude as Exports hiding (catch, catches,
23+ throwIO, try)
24+
27−import Control.Monad.Trans.Maybe as Exports (MaybeT (..), runMaybeT)
25+import Control.Monad.Trans.Maybe as Exports (MaybeT (..), runMaybeT)
28−import qualified Data.ByteString.Lazy as LB
29−import Data.Time as Exports
26+import Data.Time as Exports (UTCTime (..),
30− ( UTCTime (..),
31− diffUTCTime,
27+ diffUTCTime)
32− )
33−import UnliftIO as Exports
28+import UnliftIO as Exports (catch, catches, throwIO,
34− ( Handler,
35− catch,
36− catches,
37− throwIO,
38− try,
29+ try)
39− )
40
30
41-- | Apply a function on both elements of a pair
31-- | Apply a function on both elements of a pair
42both :: (a -> b) -> (a, a) -> (b, b)
32both :: (a -> b) -> (a, a) -> (b, b)
43both f (a, b) = (f a, f b)
33both f (a, b) = (f a, f b)
44−
45−showBS :: (Show a) => a -> ByteString
46−showBS a = encodeUtf8 (show a :: Text)
47−
48−toStrictBS :: LByteString -> ByteString
49−toStrictBS = LB.toStrict
50−
51−fromStrictBS :: ByteString -> LByteString
52−fromStrictBS = LB.fromStrict
53−
54−lastMay :: [a] -> Maybe a
55−lastMay a = last <$> nonEmpty a
src/Network/Minio.hs+218−249
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -16,237 +16,224 @@
16
16
17-- |
17-- |
18-- Module: Network.Minio
18-- Module: Network.Minio
19−-- Copyright: (c) 2017-2023 MinIO Dev Team
19+-- Copyright: (c) 2017-2019 MinIO Dev Team
20-- License: Apache 2.0
20-- License: Apache 2.0
21-- Maintainer: MinIO Dev Team <dev@min.io>
21-- Maintainer: MinIO Dev Team <dev@min.io>
22--
22--
23-- Types and functions to conveniently access S3 compatible object
23-- Types and functions to conveniently access S3 compatible object
24-- storage servers like MinIO.
24-- storage servers like MinIO.
25+
25module Network.Minio
26module Network.Minio
27+ (
26− ( -- * Credentials
28+ -- * Credentials
27− CredentialValue (..),
29+ Credentials (..)
28− credentialValueText,
30+
29− AccessKey (..),
30− SecretKey (..),
31− SessionToken (..),
32−
33− -- ** Credential Loaders
31+ -- ** Credential providers
34−
35− -- | Run actions that retrieve 'CredentialValue's from the environment or
32+ -- | Run actions that retrieve 'Credentials' from the environment or
36− -- files or other custom sources.
33+ -- files or other custom sources.
37− CredentialLoader,
34+ , Provider
38− fromAWSConfigFile,
35+ , fromAWSConfigFile
39− fromAWSEnv,
36+ , fromAWSEnv
40− fromMinioEnv,
37+ , fromMinioEnv
41− findFirst,
38+ , findFirst
42−
39+
43− -- * Connecting to object storage
40+ -- * Connecting to object storage
44− ConnectInfo,
41+ , ConnectInfo
45− setRegion,
42+ , setRegion
46− setCreds,
43+ , setCreds
47− setCredsFrom,
44+ , setCredsFrom
48− isConnectInfoSecure,
45+ , isConnectInfoSecure
49− disableTLSCertValidation,
46+ , disableTLSCertValidation
50− MinioConn,
47+ , MinioConn
51− mkMinioConn,
48+ , mkMinioConn
52−
49+
53− -- ** Connection helpers
50+ -- ** Connection helpers
54−
55− -- | These are helpers to construct 'ConnectInfo' values for common
51+ -- | These are helpers to construct 'ConnectInfo' values for common
56− -- cases.
52+ -- cases.
57− minioPlayCI,
53+ , minioPlayCI
58− awsCI,
54+ , awsCI
59− gcsCI,
55+ , gcsCI
60−
56+
61− -- ** STS Credential types
62− STSAssumeRole (..),
63− STSAssumeRoleOptions (..),
64− defaultSTSAssumeRoleOptions,
65− requestSTSCredential,
66− setSTSCredential,
67− ExpiryTime (..),
68− STSCredentialProvider,
69−
70− -- * Minio Monad
57+ -- * Minio Monad
71−
72− ----------------
58+ ----------------
73−
74− -- | The Minio Monad provides connection-reuse, bucket-location
59+ -- | The Minio Monad provides connection-reuse, bucket-location
75− -- caching, resource management and simpler error handling
60+ -- caching, resource management and simpler error handling
76− -- functionality. All actions on object storage are performed within
61+ -- functionality. All actions on object storage are performed within
77− -- this Monad.
62+ -- this Monad.
78− Minio,
63+ , Minio
79− runMinioWith,
64+ , runMinioWith
80− runMinio,
65+ , runMinio
81− runMinioResWith,
66+ , runMinioResWith
82− runMinioRes,
67+ , runMinioRes
83−
68+
84− -- * Bucket Operations
69+ -- * Bucket Operations
85−
70+
86− -- ** Creation, removal and querying
71+ -- ** Creation, removal and querying
87− Bucket,
72+ , Bucket
88− makeBucket,
73+ , makeBucket
89− removeBucket,
74+ , removeBucket
90− bucketExists,
75+ , bucketExists
91− Region,
76+ , Region
92− getLocation,
77+ , getLocation
93−
78+
94− -- ** Listing buckets
79+ -- ** Listing buckets
95− BucketInfo (..),
80+ , BucketInfo(..)
96− listBuckets,
81+ , listBuckets
97−
82+
98− -- ** Listing objects
83+ -- ** Listing objects
99− listObjects,
84+ , listObjects
100− listObjectsV1,
85+ , listObjectsV1
101− ListItem (..),
86+ , ListItem(..)
87+
102− ObjectInfo,
88+ , ObjectInfo
103− oiObject,
89+ , oiObject
104− oiModTime,
90+ , oiModTime
105− oiETag,
91+ , oiETag
106− oiSize,
92+ , oiSize
107− oiUserMetadata,
93+ , oiUserMetadata
108− oiMetadata,
94+ , oiMetadata
109−
95+
110− -- ** Listing incomplete uploads
96+ -- ** Listing incomplete uploads
111− listIncompleteUploads,
97+ , listIncompleteUploads
112− UploadId,
98+ , UploadId
113− UploadInfo (..),
99+ , UploadInfo(..)
114− listIncompleteParts,
100+ , listIncompleteParts
115− ObjectPartInfo (..),
101+ , ObjectPartInfo(..)
116−
102+
117− -- ** Bucket Notifications
103+ -- ** Bucket Notifications
118− getBucketNotification,
104+ , getBucketNotification
119− putBucketNotification,
105+ , putBucketNotification
120− removeAllBucketNotification,
106+ , removeAllBucketNotification
121− Notification (..),
107+ , Notification(..)
122− defaultNotification,
108+ , defaultNotification
123− NotificationConfig (..),
109+ , NotificationConfig(..)
124− Arn,
110+ , Arn
125− Event (..),
111+ , Event(..)
126− Filter (..),
112+ , Filter(..)
127− defaultFilter,
113+ , defaultFilter
128− FilterKey (..),
114+ , FilterKey(..)
129− defaultFilterKey,
115+ , defaultFilterKey
130− FilterRules (..),
116+ , FilterRules(..)
131− defaultFilterRules,
117+ , defaultFilterRules
132− FilterRule (..),
118+ , FilterRule(..)
133−
119+
134− -- * Object Operations
120+ -- * Object Operations
135− Object,
121+ , Object
136−
122+
137− -- ** File-based operations
123+ -- ** File-based operations
138− fGetObject,
124+ , fGetObject
139− fPutObject,
125+ , fPutObject
140−
126+
141− -- ** Conduit-based streaming operations
127+ -- ** Conduit-based streaming operations
142− putObject,
128+ , putObject
143− PutObjectOptions,
129+ , PutObjectOptions
144− defaultPutObjectOptions,
130+ , defaultPutObjectOptions
145− pooContentType,
131+ , pooContentType
146− pooContentEncoding,
132+ , pooContentEncoding
147− pooContentDisposition,
133+ , pooContentDisposition
148− pooContentLanguage,
134+ , pooContentLanguage
149− pooCacheControl,
135+ , pooCacheControl
150− pooStorageClass,
136+ , pooStorageClass
151− pooUserMetadata,
137+ , pooUserMetadata
152− pooNumThreads,
138+ , pooNumThreads
153− pooSSE,
139+ , pooSSE
140+
154− getObject,
141+ , getObject
155− GetObjectOptions,
142+ , GetObjectOptions
156− defaultGetObjectOptions,
143+ , defaultGetObjectOptions
157− gooRange,
144+ , gooRange
158− gooIfMatch,
145+ , gooIfMatch
159− gooIfNoneMatch,
146+ , gooIfNoneMatch
160− gooIfModifiedSince,
147+ , gooIfModifiedSince
161− gooIfUnmodifiedSince,
148+ , gooIfUnmodifiedSince
162− gooSSECKey,
149+ , gooSSECKey
163− GetObjectResponse,
150+ , GetObjectResponse
164− gorObjectInfo,
151+ , gorObjectInfo
165− gorObjectStream,
152+ , gorObjectStream
166−
153+
167− -- ** Server-side object copying
154+ -- ** Server-side object copying
168− copyObject,
155+ , copyObject
169− SourceInfo,
156+ , SourceInfo
170− defaultSourceInfo,
157+ , defaultSourceInfo
171− srcBucket,
158+ , srcBucket
172− srcObject,
159+ , srcObject
173− srcRange,
160+ , srcRange
174− srcIfMatch,
161+ , srcIfMatch
175− srcIfNoneMatch,
162+ , srcIfNoneMatch
176− srcIfModifiedSince,
163+ , srcIfModifiedSince
177− srcIfUnmodifiedSince,
164+ , srcIfUnmodifiedSince
178− DestinationInfo,
165+ , DestinationInfo
179− defaultDestinationInfo,
166+ , defaultDestinationInfo
180− dstBucket,
167+ , dstBucket
181− dstObject,
168+ , dstObject
182−
169+
183− -- ** Querying object info
170+ -- ** Querying object info
184− statObject,
171+ , statObject
185−
172+
186− -- ** Object removal operations
173+ -- ** Object removal operations
187− removeObject,
174+ , removeObject
188− removeIncompleteUpload,
175+ , removeIncompleteUpload
189−
176+
190− -- ** Select Object Content with SQL
177+ -- ** Select Object Content with SQL
191− module Network.Minio.SelectAPI,
178+ , module Network.Minio.SelectAPI
192−
179+
193− -- * Server-Side Encryption Helpers
180+ -- * Server-Side Encryption Helpers
194− mkSSECKey,
181+ , mkSSECKey
195− SSECKey,
182+ , SSECKey
196− SSE (..),
183+ , SSE(..)
197−
184+
198− -- * Presigned Operations
185+ -- * Presigned Operations
199− presignedPutObjectUrl,
186+ , presignedPutObjectUrl
200− presignedGetObjectUrl,
187+ , presignedGetObjectUrl
201− presignedHeadObjectUrl,
188+ , presignedHeadObjectUrl
202− UrlExpiry,
189+ , UrlExpiry
203−
190+
204− -- ** POST (browser) upload helpers
191+ -- ** POST (browser) upload helpers
205−
206− -- | Please see
192+ -- | Please see
207− -- https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
193+ -- https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
208− -- for detailed information.
194+ -- for detailed information.
209− newPostPolicy,
195+ , newPostPolicy
210− presignedPostPolicy,
196+ , presignedPostPolicy
211− showPostPolicy,
197+ , showPostPolicy
212− PostPolicy,
198+ , PostPolicy
213− PostPolicyError (..),
199+ , PostPolicyError(..)
214−
200+
215− -- *** Post Policy condition helpers
201+ -- *** Post Policy condition helpers
216− PostPolicyCondition,
202+ , PostPolicyCondition
217− ppCondBucket,
203+ , ppCondBucket
218− ppCondContentLengthRange,
204+ , ppCondContentLengthRange
219− ppCondContentType,
205+ , ppCondContentType
220− ppCondKey,
206+ , ppCondKey
221− ppCondKeyStartsWith,
207+ , ppCondKeyStartsWith
222− ppCondSuccessActionStatus,
208+ , ppCondSuccessActionStatus
223−
209+
224− -- * Error handling
210+ -- * Error handling
225−
226− -- | Data types representing various errors that may occur while
211+ -- | Data types representing various errors that may occur while
227− -- working with an object storage service.
212+ -- working with an object storage service.
228− MinioErr (..),
213+ , MinioErr(..)
229− MErrV (..),
214+ , MErrV(..)
230− ServiceErr (..),
215+ , ServiceErr(..)
231− )
216+
232−where
217+) where
233
218
234{-
219{-
235This module exports the high-level MinIO API for object storage.
220This module exports the high-level MinIO API for object storage.
236-}
221-}
237
222
238−import qualified Data.Conduit as C
223+import qualified Data.Conduit as C
239−import qualified Data.Conduit.Binary as CB
224+import qualified Data.Conduit.Binary as CB
240import qualified Data.Conduit.Combinators as CC
225import qualified Data.Conduit.Combinators as CC
241−import Network.Minio.API
226+
227+import Lib.Prelude
228+
242−import Network.Minio.CopyObject
229+import Network.Minio.CopyObject
243−import Network.Minio.Credentials
244−import Network.Minio.Data
230+import Network.Minio.Data
245−import Network.Minio.Errors
231+import Network.Minio.Errors
246−import Network.Minio.ListOps
232+import Network.Minio.ListOps
247−import Network.Minio.PutObject
233+import Network.Minio.PutObject
248−import Network.Minio.S3API
234+import Network.Minio.S3API
249−import Network.Minio.SelectAPI
235+import Network.Minio.SelectAPI
236+import Network.Minio.Utils
250
237
251-- | Lists buckets.
238-- | Lists buckets.
252listBuckets :: Minio [BucketInfo]
239listBuckets :: Minio [BucketInfo]
@@ -261,12 +248,8 @@ fGetObject bucket object fp opts = do
261 C.connect (gorObjectStream src) $ CB.sinkFileCautious fp
248 C.connect (gorObjectStream src) $ CB.sinkFileCautious fp
262
249
263-- | Upload the given file to the given object.
250-- | Upload the given file to the given object.
264−fPutObject ::
251+fPutObject :: Bucket -> Object -> FilePath
265− Bucket ->
266− Object ->
267− FilePath ->
268− PutObjectOptions ->
252+ -> PutObjectOptions -> Minio ()
269− Minio ()
270fPutObject bucket object f opts =
253fPutObject bucket object f opts =
271 void $ putObjectInternal bucket object opts $ ODFile f Nothing
254 void $ putObjectInternal bucket object opts $ ODFile f Nothing
272
255
@@ -274,13 +257,8 @@ fPutObject bucket object f opts =
274-- known; this helps the library select optimal part sizes to perform
257-- known; this helps the library select optimal part sizes to perform
275-- a multipart upload. If not specified, it is assumed that the object
258-- a multipart upload. If not specified, it is assumed that the object
276-- can be potentially 5TiB and selects multipart sizes appropriately.
259-- can be potentially 5TiB and selects multipart sizes appropriately.
277−putObject ::
278− Bucket ->
279− Object ->
280− C.ConduitM () ByteString Minio () ->
260+putObject :: Bucket -> Object -> C.ConduitM () ByteString Minio ()
281− Maybe Int64 ->
282− PutObjectOptions ->
261+ -> Maybe Int64 -> PutObjectOptions -> Minio ()
283− Minio ()
284putObject bucket object src sizeMay opts =
262putObject bucket object src sizeMay opts =
285 void $ putObjectInternal bucket object opts $ ODStream src sizeMay
263 void $ putObjectInternal bucket object opts $ ODStream src sizeMay
286
264
@@ -290,25 +268,18 @@ putObject bucket object src sizeMay opts =
290-- copy operation if the new object is to be greater than 5GiB in
268-- copy operation if the new object is to be greater than 5GiB in
291-- size.
269-- size.
292copyObject :: DestinationInfo -> SourceInfo -> Minio ()
270copyObject :: DestinationInfo -> SourceInfo -> Minio ()
293−copyObject dstInfo srcInfo =
271+copyObject dstInfo srcInfo = void $ copyObjectInternal (dstBucket dstInfo)
294− void $
295− copyObjectInternal
296− (dstBucket dstInfo)
297− (dstObject dstInfo)
272+ (dstObject dstInfo) srcInfo
298− srcInfo
299
273
300-- | Remove an object from the object store.
274-- | Remove an object from the object store.
301removeObject :: Bucket -> Object -> Minio ()
275removeObject :: Bucket -> Object -> Minio ()
302removeObject = deleteObject
276removeObject = deleteObject
303
277
304-- | Get an object from the object store.
278-- | Get an object from the object store.
305−getObject ::
306− Bucket ->
307− Object ->
308− GetObjectOptions ->
279+getObject :: Bucket -> Object -> GetObjectOptions
309− Minio GetObjectResponse
280+ -> Minio GetObjectResponse
310getObject bucket object opts =
281getObject bucket object opts =
311− getObject' bucket object [] $ gooToHeaders opts
282+ getObject' bucket object [] $ gooToHeaders opts
312
283
313-- | Get an object's metadata from the object store. It accepts the
284-- | Get an object's metadata from the object store. It accepts the
314-- same options as GetObject.
285-- same options as GetObject.
@@ -338,8 +309,6 @@ bucketExists = headBucket
338-- | Removes an ongoing multipart upload of an object.
309-- | Removes an ongoing multipart upload of an object.
339removeIncompleteUpload :: Bucket -> Object -> Minio ()
310removeIncompleteUpload :: Bucket -> Object -> Minio ()
340removeIncompleteUpload bucket object = do
311removeIncompleteUpload bucket object = do
341− uploads <-
342− C.runConduit $
343− listIncompleteUploads bucket (Just object) False
312+ uploads <- C.runConduit $ listIncompleteUploads bucket (Just object) False
344− C..| CC.sinkList
313+ C..| CC.sinkList
345 mapM_ (abortMultipartUpload bucket object) (uiUploadId <$> uploads)
314 mapM_ (abortMultipartUpload bucket object) (uiUploadId <$> uploads)
src/Network/Minio/API.hs+159−259
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017, 2018 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -15,260 +15,169 @@
15--
15--
16
16
17module Network.Minio.API
17module Network.Minio.API
18− ( connect,
18+ ( connect
19− S3ReqInfo (..),
19+ , S3ReqInfo(..)
20− runMinio,
20+ , runMinio
21− executeRequest,
21+ , executeRequest
22− buildRequest,
23− mkStreamRequest,
22+ , mkStreamRequest
24− getLocation,
23+ , getLocation
24+
25− isValidBucketName,
25+ , isValidBucketName
26− checkBucketNameValidity,
26+ , checkBucketNameValidity
27− isValidObjectName,
27+ , isValidObjectName
28− checkObjectNameValidity,
28+ , checkObjectNameValidity
29− requestSTSCredential,
30− )
31−where
29+ ) where
32−
30+
33−import Control.Retry
31+import Control.Retry (fullJitterBackoff,
34− ( fullJitterBackoff,
35− limitRetriesByCumulativeDelay,
32+ limitRetriesByCumulativeDelay,
36− retrying,
33+ retrying)
37− )
38−import qualified Data.ByteString as B
34+import qualified Data.ByteString as B
39−import qualified Data.Char as C
35+import qualified Data.Char as C
40−import qualified Data.Conduit as C
36+import qualified Data.Conduit as C
41−import qualified Data.HashMap.Strict as H
37+import qualified Data.HashMap.Strict as H
42−import qualified Data.Text as T
38+import qualified Data.Text as T
43−import qualified Data.Time.Clock as Time
39+import qualified Data.Time.Clock as Time
44−import Lib.Prelude
45−import Network.HTTP.Client (defaultManagerSettings)
46−import qualified Network.HTTP.Client as NClient
47−import Network.HTTP.Conduit (Response)
40+import Network.HTTP.Conduit (Response)
48−import qualified Network.HTTP.Conduit as NC
41+import qualified Network.HTTP.Conduit as NC
49−import Network.HTTP.Types (simpleQueryToQuery)
50−import qualified Network.HTTP.Types as HT
42+import qualified Network.HTTP.Types as HT
51−import Network.HTTP.Types.Header (hHost)
43+import Network.HTTP.Types.Header (hHost)
44+
45+import Lib.Prelude
46+
52−import Network.Minio.APICommon
47+import Network.Minio.APICommon
53−import Network.Minio.Credentials
54−import Network.Minio.Data
48+import Network.Minio.Data
55−import Network.Minio.Errors
49+import Network.Minio.Errors
56−import Network.Minio.Sign.V4
50+import Network.Minio.Sign.V4
57−import Network.Minio.Utils
51+import Network.Minio.Utils
58−import Network.Minio.XmlParser
52+import Network.Minio.XmlParser
59
53
60-- | Fetch bucket location (region)
54-- | Fetch bucket location (region)
61getLocation :: Bucket -> Minio Region
55getLocation :: Bucket -> Minio Region
62getLocation bucket = do
56getLocation bucket = do
63− resp <-
64− executeRequest $
65− defaultS3ReqInfo
57+ resp <- executeRequest $ defaultS3ReqInfo {
66− { riBucket = Just bucket,
58+ riBucket = Just bucket
67− riQueryParams = [("location", Nothing)],
59+ , riQueryParams = [("location", Nothing)]
68− riNeedsLocation = False
60+ , riNeedsLocation = False
69− }
61+ }
70 parseLocation $ NC.responseBody resp
62 parseLocation $ NC.responseBody resp
71
63
64+
72-- | Looks for region in RegionMap and updates it using getLocation if
65-- | Looks for region in RegionMap and updates it using getLocation if
73-- absent.
66-- absent.
74discoverRegion :: S3ReqInfo -> Minio (Maybe Region)
67discoverRegion :: S3ReqInfo -> Minio (Maybe Region)
75discoverRegion ri = runMaybeT $ do
68discoverRegion ri = runMaybeT $ do
76 bucket <- MaybeT $ return $ riBucket ri
69 bucket <- MaybeT $ return $ riBucket ri
77 regionMay <- lift $ lookupRegionCache bucket
70 regionMay <- lift $ lookupRegionCache bucket
78− maybe
71+ maybe (do
79− ( do
80− l <- lift $ getLocation bucket
72+ l <- lift $ getLocation bucket
81− lift $ addToRegionCache bucket l
73+ lift $ addToRegionCache bucket l
82− return l
74+ return l
83− )
84− return
85− regionMay
75+ ) return regionMay
86−
76+
87−-- | Returns the region to be used for the request.
88getRegion :: S3ReqInfo -> Minio (Maybe Region)
77getRegion :: S3ReqInfo -> Minio (Maybe Region)
89getRegion ri = do
78getRegion ri = do
90− ci <- asks mcConnInfo
79+ ci <- asks mcConnInfo
91−
80+
92− -- getService/makeBucket/getLocation -- don't need location
81+ -- getService/makeBucket/getLocation -- don't need location
93− if
94− | not $ riNeedsLocation ri ->
82+ if | not $ riNeedsLocation ri ->
95− return $ Just $ connectRegion ci
83+ return $ Just $ connectRegion ci
84+
96− -- if autodiscovery of location is disabled by user
85+ -- if autodiscovery of location is disabled by user
97− | not $ connectAutoDiscoverRegion ci ->
86+ | not $ connectAutoDiscoverRegion ci ->
98− return $ Just $ connectRegion ci
87+ return $ Just $ connectRegion ci
88+
99− -- discover the region for the request
89+ -- discover the region for the request
100− | otherwise -> discoverRegion ri
90+ | otherwise -> discoverRegion ri
101
91
102getRegionHost :: Region -> Minio Text
92getRegionHost :: Region -> Minio Text
103getRegionHost r = do
93getRegionHost r = do
104− ci <- asks mcConnInfo
94+ ci <- asks mcConnInfo
105−
106− if "amazonaws.com" `T.isSuffixOf` connectHost ci
107− then
108− maybe
109− (throwIO $ MErrVRegionNotSupported r)
110− return
111− (H.lookup r awsRegionMap)
112− else return $ connectHost ci
113
95
114−-- | Computes the appropriate host, path and region for the request.
96+ if "amazonaws.com" `T.isSuffixOf` connectHost ci
115−--
97+ then maybe (throwIO $ MErrVRegionNotSupported r)
116−-- For AWS, always use virtual bucket style, unless bucket has periods. For
98+ return (H.lookup r awsRegionMap)
117−-- MinIO and other non-AWS, default to path style.
99+ else return $ connectHost ci
118−getHostPathRegion :: S3ReqInfo -> Minio (Text, ByteString, Maybe Region)
119−getHostPathRegion ri = do
120− ci <- asks mcConnInfo
121− regionMay <- getRegion ri
122− case riBucket ri of
123− Nothing ->
124− -- Implies a ListBuckets request.
125− return (connectHost ci, "/", regionMay)
126− Just bucket -> do
127− regionHost <- case regionMay of
128− Nothing -> return $ connectHost ci
129− Just "" -> return $ connectHost ci
130− Just r -> getRegionHost r
131− let pathStyle =
132− ( regionHost,
133− getS3Path (riBucket ri) (riObject ri),
134− regionMay
135− )
136− virtualStyle =
137− ( bucket <> "." <> regionHost,
138− encodeUtf8 $ "/" <> fromMaybe "" (riObject ri),
139− regionMay
140− )
141− ( if isAWSConnectInfo ci
142− then
143− return $
144− if bucketHasPeriods bucket
145− then pathStyle
146− else virtualStyle
147− else return pathStyle
148− )
149−
150−-- | requestSTSCredential requests temporary credentials using the Security Token
151−-- Service API. The returned credential will include a populated 'SessionToken'
152−-- and an 'ExpiryTime'.
153−requestSTSCredential :: (STSCredentialProvider p) => p -> IO (CredentialValue, ExpiryTime)
154−requestSTSCredential p = do
155− endpoint <- maybe (throwIO $ MErrValidation MErrVSTSEndpointNotFound) return $ getSTSEndpoint p
156− let endPt = NC.parseRequest_ $ toString endpoint
157− settings
158− | NC.secure endPt = NC.tlsManagerSettings
159− | otherwise = defaultManagerSettings
160−
161− mgr <- NC.newManager settings
162− liftIO $ retrieveSTSCredentials p ("", 0, False) mgr
163
100
164buildRequest :: S3ReqInfo -> Minio NC.Request
101buildRequest :: S3ReqInfo -> Minio NC.Request
165buildRequest ri = do
102buildRequest ri = do
166− maybe (return ()) checkBucketNameValidity $ riBucket ri
103+ maybe (return ()) checkBucketNameValidity $ riBucket ri
167− maybe (return ()) checkObjectNameValidity $ riObject ri
104+ maybe (return ()) checkObjectNameValidity $ riObject ri
168−
105+
169− ci <- asks mcConnInfo
106+ ci <- asks mcConnInfo
170−
107+
171− (host, path, regionMay) <- getHostPathRegion ri
108+ regionMay <- getRegion ri
172−
109+
110+ regionHost <- maybe (return $ connectHost ci) getRegionHost regionMay
111+
112+ let ri' = ri { riHeaders = hostHeader : riHeaders ri
113+ , riRegion = regionMay
114+ }
173− let ci' = ci {connectHost = host}
115+ ci' = ci { connectHost = regionHost }
174− hostHeader = (hHost, getHostAddr ci')
116+ hostHeader = (hHost, getHostAddr ci')
175− ri' =
117+
176− ri
177− { riHeaders = hostHeader : riHeaders ri,
178− riRegion = regionMay
179− }
180− -- Does not contain body and auth info.
118+ -- Does not contain body and auth info.
181− baseRequest =
182− NC.defaultRequest
119+ baseRequest = NC.defaultRequest
183− { NC.method = riMethod ri',
120+ { NC.method = riMethod ri'
184− NC.secure = connectIsSecure ci',
121+ , NC.secure = connectIsSecure ci'
185− NC.host = encodeUtf8 $ connectHost ci',
122+ , NC.host = encodeUtf8 $ connectHost ci'
186− NC.port = connectPort ci',
123+ , NC.port = connectPort ci'
187− NC.path = path,
124+ , NC.path = getS3Path (riBucket ri') (riObject ri')
188− NC.requestHeaders = riHeaders ri',
125+ , NC.requestHeaders = riHeaders ri'
189− NC.queryString = HT.renderQuery False $ riQueryParams ri'
126+ , NC.queryString = HT.renderQuery False $ riQueryParams ri'
190− }
127+ }
191−
128+
192− timeStamp <- liftIO Time.getCurrentTime
129+ timeStamp <- liftIO Time.getCurrentTime
130+
131+ let sp = SignParams (connectAccessKey ci') (connectSecretKey ci')
132+ timeStamp (riRegion ri') Nothing Nothing
133+
134+ -- Cases to handle:
135+ --
136+ -- 1. Connection is secure: use unsigned payload
137+ --
138+ -- 2. Insecure connection, streaming signature is enabled via use of
139+ -- conduit payload: use streaming signature for request.
140+ --
141+ -- 3. Insecure connection, non-conduit payload: compute payload
142+ -- sha256hash, buffer request in memory and perform request.
143+
144+ -- case 2 from above.
145+ if | isStreamingPayload (riPayload ri') &&
146+ (not $ connectIsSecure ci') -> do
147+ (pLen, pSrc) <- case riPayload ri of
148+ PayloadC l src -> return (l, src)
149+ _ -> throwIO MErrVUnexpectedPayload
150+ let reqFn = signV4Stream pLen sp baseRequest
151+ return $ reqFn pSrc
152+
153+ | otherwise -> do
154+ -- case 1 described above.
155+ sp' <- if | connectIsSecure ci' -> return sp
156+ -- case 3 described above.
157+ | otherwise -> do
158+ pHash <- getPayloadSHA256Hash $ riPayload ri'
159+ return $ sp { spPayloadHash = Just pHash }
160+
161+ let signHeaders = signV4 sp' baseRequest
162+ return $ baseRequest
163+ { NC.requestHeaders =
164+ NC.requestHeaders baseRequest ++
165+ mkHeaderFromPairs signHeaders
166+ , NC.requestBody = getRequestBody (riPayload ri')
167+ }
193
168
194− mgr <- asks mcConnManager
195− cv <- liftIO $ getCredential (connectCreds ci') (getEndpoint ci') mgr
196−
197− let sp =
198− SignParams
199− (coerce $ cvAccessKey cv)
200− (coerce $ cvSecretKey cv)
201− (coerce $ cvSessionToken cv)
202− ServiceS3
203− timeStamp
204− (riRegion ri')
205− (riPresignExpirySecs ri')
206− Nothing
207−
208− -- Cases to handle:
209− --
210− -- 0. Handle presign URL case.
211− --
212− -- 1. Connection is secure: use unsigned payload
213− --
214− -- 2. Insecure connection, streaming signature is enabled via use of
215− -- conduit payload: use streaming signature for request.
216− --
217− -- 3. Insecure connection, non-conduit payload: compute payload
218− -- sha256hash, buffer request in memory and perform request.
219−
220− if
221− | isJust (riPresignExpirySecs ri') ->
222− -- case 0 from above.
223− do
224− let signPairs = signV4QueryParams sp baseRequest
225− qpToAdd = simpleQueryToQuery signPairs
226− existingQueryParams = HT.parseQuery (NC.queryString baseRequest)
227− updatedQueryParams = existingQueryParams ++ qpToAdd
228− return $ NClient.setQueryString updatedQueryParams baseRequest
229− | isStreamingPayload (riPayload ri') && not (connectIsSecure ci') ->
230− -- case 2 from above.
231− do
232− (pLen, pSrc) <- case riPayload ri of
233− PayloadC l src -> return (l, src)
234− _ -> throwIO MErrVUnexpectedPayload
235− let reqFn = signV4Stream pLen sp baseRequest
236− return $ reqFn pSrc
237− | otherwise ->
238− do
239− sp' <-
240− ( if connectIsSecure ci'
241− then -- case 1 described above.
242− return sp
243− else
244− ( -- case 3 described above.
245− do
246− pHash <- getPayloadSHA256Hash $ riPayload ri'
247− return $ sp {spPayloadHash = Just pHash}
248− )
249− )
250−
251− let signHeaders = signV4 sp' baseRequest
252− return $
253− baseRequest
254− { NC.requestHeaders =
255− NC.requestHeaders baseRequest ++ signHeaders,
256− NC.requestBody = getRequestBody (riPayload ri')
257− }
258
169
259retryAPIRequest :: Minio a -> Minio a
170retryAPIRequest :: Minio a -> Minio a
260retryAPIRequest apiCall = do
171retryAPIRequest apiCall = do
261− resE <-
262− retrying retryPolicy (const shouldRetry) $
172+ resE <- retrying retryPolicy (const shouldRetry) $
263− const $
264− try apiCall
173+ const $ try apiCall
265 either throwIO return resE
174 either throwIO return resE
266 where
175 where
267 -- Retry using the full-jitter backoff method for up to 10 mins
176 -- Retry using the full-jitter backoff method for up to 10 mins
268 -- total
177 -- total
269− retryPolicy =
270− limitRetriesByCumulativeDelay tenMins $
178+ retryPolicy = limitRetriesByCumulativeDelay tenMins
271− fullJitterBackoff oneMilliSecond
179+ $ fullJitterBackoff oneMilliSecond
180+
272 oneMilliSecond = 1000 -- in microseconds
181 oneMilliSecond = 1000 -- in microseconds
273 tenMins = 10 * 60 * 1000000 -- in microseconds
182 tenMins = 10 * 60 * 1000000 -- in microseconds
274 -- retry on connection related failure
183 -- retry on connection related failure
@@ -280,23 +189,23 @@ retryAPIRequest apiCall = do
280 -- API request failed with a retryable exception
189 -- API request failed with a retryable exception
281 Left httpExn@(NC.HttpExceptionRequest _ exn) ->
190 Left httpExn@(NC.HttpExceptionRequest _ exn) ->
282 case (exn :: NC.HttpExceptionContent) of
191 case (exn :: NC.HttpExceptionContent) of
283− NC.ResponseTimeout -> return True
192+ NC.ResponseTimeout -> return True
284− NC.ConnectionTimeout -> return True
193+ NC.ConnectionTimeout -> return True
285 NC.ConnectionFailure _ -> return True
194 NC.ConnectionFailure _ -> return True
286 -- We received an unexpected exception
195 -- We received an unexpected exception
287− _ -> throwIO httpExn
196+ _ -> throwIO httpExn
288 -- We received an unexpected exception
197 -- We received an unexpected exception
289 Left someOtherExn -> throwIO someOtherExn
198 Left someOtherExn -> throwIO someOtherExn
290
199
200+
291executeRequest :: S3ReqInfo -> Minio (Response LByteString)
201executeRequest :: S3ReqInfo -> Minio (Response LByteString)
292executeRequest ri = do
202executeRequest ri = do
293 req <- buildRequest ri
203 req <- buildRequest ri
294 mgr <- asks mcConnManager
204 mgr <- asks mcConnManager
295 retryAPIRequest $ httpLbs req mgr
205 retryAPIRequest $ httpLbs req mgr
296
206
297−mkStreamRequest ::
207+mkStreamRequest :: S3ReqInfo
298− S3ReqInfo ->
299− Minio (Response (C.ConduitM () ByteString Minio ()))
208+ -> Minio (Response (C.ConduitM () ByteString Minio ()))
300mkStreamRequest ri = do
209mkStreamRequest ri = do
301 req <- buildRequest ri
210 req <- buildRequest ri
302 mgr <- asks mcConnManager
211 mgr <- asks mcConnManager
@@ -305,50 +214,41 @@ mkStreamRequest ri = do
305-- Bucket name validity check according to AWS rules.
214-- Bucket name validity check according to AWS rules.
306isValidBucketName :: Bucket -> Bool
215isValidBucketName :: Bucket -> Bool
307isValidBucketName bucket =
216isValidBucketName bucket =
308− not
309− ( or
310− [ len < 3 || len > 63,
217+ not (or [ len < 3 || len > 63
311− any labelCheck labels,
218+ , or (map labelCheck labels)
312− any labelCharsCheck labels,
219+ , or (map labelCharsCheck labels)
313− isIPCheck
220+ , isIPCheck
314− ]
221+ ])
315− )
316 where
222 where
317 len = T.length bucket
223 len = T.length bucket
318 labels = T.splitOn "." bucket
224 labels = T.splitOn "." bucket
225+
319 -- does label `l` fail basic checks of length and start/end?
226 -- does label `l` fail basic checks of length and start/end?
320 labelCheck l = T.length l == 0 || T.head l == '-' || T.last l == '-'
227 labelCheck l = T.length l == 0 || T.head l == '-' || T.last l == '-'
228+
321 -- does label `l` have non-allowed characters?
229 -- does label `l` have non-allowed characters?
322− labelCharsCheck l =
230+ labelCharsCheck l = isJust $ T.find (\x -> not (C.isAsciiLower x ||
323− isJust $
324− T.find
325− ( \x ->
326− not
327− ( C.isAsciiLower x
328− || x == '-'
231+ x == '-' ||
329− || C.isDigit x
232+ C.isDigit x)) l
330− )
233+
331− )
332− l
333 -- does label `l` have non-digit characters?
234 -- does label `l` have non-digit characters?
334 labelNonDigits l = isJust $ T.find (not . C.isDigit) l
235 labelNonDigits l = isJust $ T.find (not . C.isDigit) l
335 labelAsNums = map (not . labelNonDigits) labels
236 labelAsNums = map (not . labelNonDigits) labels
237+
336 -- check if bucket name looks like an IP
238 -- check if bucket name looks like an IP
337 isIPCheck = and labelAsNums && length labelAsNums == 4
239 isIPCheck = and labelAsNums && length labelAsNums == 4
338
240
339-- Throws exception iff bucket name is invalid according to AWS rules.
241-- Throws exception iff bucket name is invalid according to AWS rules.
340−checkBucketNameValidity :: (MonadIO m) => Bucket -> m ()
242+checkBucketNameValidity :: MonadIO m => Bucket -> m ()
341checkBucketNameValidity bucket =
243checkBucketNameValidity bucket =
342− unless (isValidBucketName bucket) $
244+ when (not $ isValidBucketName bucket) $
343− throwIO $
344− MErrVInvalidBucketName bucket
245+ throwIO $ MErrVInvalidBucketName bucket
345
246
346isValidObjectName :: Object -> Bool
247isValidObjectName :: Object -> Bool
347isValidObjectName object =
248isValidObjectName object =
348 T.length object > 0 && B.length (encodeUtf8 object) <= 1024
249 T.length object > 0 && B.length (encodeUtf8 object) <= 1024
349
250
350−checkObjectNameValidity :: (MonadIO m) => Object -> m ()
251+checkObjectNameValidity :: MonadIO m => Object -> m ()
351checkObjectNameValidity object =
252checkObjectNameValidity object =
352− unless (isValidObjectName object) $
253+ when (not $ isValidObjectName object) $
353− throwIO $
354− MErrVInvalidObjectName object
254+ throwIO $ MErrVInvalidObjectName object
src/Network/Minio/APICommon.hs+24−36
@@ -16,39 +16,37 @@
16
16
17module Network.Minio.APICommon where
17module Network.Minio.APICommon where
18
18
19−import qualified Conduit as C
19+import qualified Conduit as C
20−import qualified Data.ByteString as BS
20+import qualified Data.ByteString as BS
21−import qualified Data.ByteString.Lazy as LB
21+import qualified Data.ByteString.Lazy as LB
22−import Data.Conduit.Binary (sourceHandleRange)
22+import Data.Conduit.Binary (sourceHandleRange)
23−import qualified Data.Text as T
24−import Lib.Prelude
25−import qualified Network.HTTP.Conduit as NC
23+import qualified Network.HTTP.Conduit as NC
26−import qualified Network.HTTP.Types as HT
24+import qualified Network.HTTP.Types as HT
25+
26+import Lib.Prelude
27+
27−import Network.Minio.Data
28+import Network.Minio.Data
28−import Network.Minio.Data.Crypto
29+import Network.Minio.Data.Crypto
29−import Network.Minio.Errors
30+import Network.Minio.Errors
30
31
31sha256Header :: ByteString -> HT.Header
32sha256Header :: ByteString -> HT.Header
32−sha256Header = ("x-amz-content-sha256",)
33+sha256Header = ("x-amz-content-sha256", )
33
34
34-- | This function throws an error if the payload is a conduit (as it
35-- | This function throws an error if the payload is a conduit (as it
35-- will not be possible to re-read the conduit after it is consumed).
36-- will not be possible to re-read the conduit after it is consumed).
36getPayloadSHA256Hash :: Payload -> Minio ByteString
37getPayloadSHA256Hash :: Payload -> Minio ByteString
37getPayloadSHA256Hash (PayloadBS bs) = return $ hashSHA256 bs
38getPayloadSHA256Hash (PayloadBS bs) = return $ hashSHA256 bs
38−getPayloadSHA256Hash (PayloadH h off size) =
39+getPayloadSHA256Hash (PayloadH h off size) = hashSHA256FromSource $
39− hashSHA256FromSource $
40− sourceHandleRange
40+ sourceHandleRange h
41− h
42− (return . fromIntegral $ off)
41+ (return . fromIntegral $ off)
43− (return . fromIntegral $ size)
42+ (return . fromIntegral $ size)
44getPayloadSHA256Hash (PayloadC _ _) = throwIO MErrVUnexpectedPayload
43getPayloadSHA256Hash (PayloadC _ _) = throwIO MErrVUnexpectedPayload
45
44
46getRequestBody :: Payload -> NC.RequestBody
45getRequestBody :: Payload -> NC.RequestBody
47getRequestBody (PayloadBS bs) = NC.RequestBodyBS bs
46getRequestBody (PayloadBS bs) = NC.RequestBodyBS bs
48getRequestBody (PayloadH h off size) =
47getRequestBody (PayloadH h off size) =
49− NC.requestBodySource size $
48+ NC.requestBodySource (fromIntegral size) $
50− sourceHandleRange
49+ sourceHandleRange h
51− h
52 (return . fromIntegral $ off)
50 (return . fromIntegral $ off)
53 (return . fromIntegral $ size)
51 (return . fromIntegral $ size)
54getRequestBody (PayloadC n src) = NC.requestBodySource n src
52getRequestBody (PayloadC n src) = NC.requestBodySource n src
@@ -57,24 +55,14 @@ mkStreamingPayload :: Payload -> Payload
57mkStreamingPayload payload =
55mkStreamingPayload payload =
58 case payload of
56 case payload of
59 PayloadBS bs ->
57 PayloadBS bs ->
60− PayloadC
61− (fromIntegral $ BS.length bs)
58+ PayloadC (fromIntegral $ BS.length bs)
62 (C.sourceLazy $ LB.fromStrict bs)
59 (C.sourceLazy $ LB.fromStrict bs)
63 PayloadH h off len ->
60 PayloadH h off len ->
64− PayloadC len $
65− sourceHandleRange
61+ PayloadC len $ sourceHandleRange h
66− h
67− (return . fromIntegral $ off)
62+ (return . fromIntegral $ off)
68− (return . fromIntegral $ len)
63+ (return . fromIntegral $ len)
69 _ -> payload
64 _ -> payload
70
65
71isStreamingPayload :: Payload -> Bool
66isStreamingPayload :: Payload -> Bool
72isStreamingPayload (PayloadC _ _) = True
67isStreamingPayload (PayloadC _ _) = True
73−isStreamingPayload _ = False
68+isStreamingPayload _ = False
74−
75−-- | Checks if the connect info is for Amazon S3.
76−isAWSConnectInfo :: ConnectInfo -> Bool
77−isAWSConnectInfo ci = ".amazonaws.com" `T.isSuffixOf` connectHost ci
78−
79−bucketHasPeriods :: Bucket -> Bool
80−bucketHasPeriods b = isJust $ T.find (== '.') b
src/Network/Minio/AdminAPI.hs+391−543
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2018-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2018 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -16,341 +16,270 @@
16
16
17module Network.Minio.AdminAPI
17module Network.Minio.AdminAPI
18 ( -- * MinIO Admin API
18 ( -- * MinIO Admin API
19−
20 --------------------
19 --------------------
21−
22 -- | Provides MinIO admin API and related types. It is in
20 -- | Provides MinIO admin API and related types. It is in
23 -- experimental state.
21 -- experimental state.
24− DriveInfo (..),
22+ DriveInfo(..)
25− ErasureInfo (..),
23+ , ErasureInfo(..)
26− Backend (..),
24+ , Backend(..)
27− ConnStats (..),
25+ , ConnStats(..)
28− HttpStats (..),
26+ , HttpStats(..)
29− ServerProps (..),
27+ , ServerProps(..)
30− CountNAvgTime (..),
28+ , CountNAvgTime(..)
31− StorageClass (..),
29+ , StorageClass(..)
32− StorageInfo (..),
30+ , StorageInfo(..)
33− SIData (..),
31+ , SIData(..)
34− ServerInfo (..),
32+ , ServerInfo(..)
35− getServerInfo,
33+ , getServerInfo
34+
36− HealOpts (..),
35+ , HealOpts(..)
37− HealResultItem (..),
36+ , HealResultItem(..)
38− HealStatus (..),
37+ , HealStatus(..)
39− HealStartResp (..),
38+ , HealStartResp(..)
40− startHeal,
39+ , startHeal
41− forceStartHeal,
40+ , forceStartHeal
42− getHealStatus,
41+ , getHealStatus
42+
43− SetConfigResult (..),
43+ , SetConfigResult(..)
44− NodeSummary (..),
44+ , NodeSummary(..)
45− setConfig,
45+ , setConfig
46− getConfig,
46+ , getConfig
47+
47− ServerVersion (..),
48+ , ServerVersion(..)
48− ServiceStatus (..),
49+ , ServiceStatus(..)
49− serviceStatus,
50+ , serviceStatus
51+
50− ServiceAction (..),
52+ , ServiceAction(..)
51− serviceSendAction,
53+ , serviceSendAction
52− )
53−where
54+ ) where
54−
55+
55−import Data.Aeson
56+import Data.Aeson (FromJSON, ToJSON, Value (Object),
56− ( FromJSON,
57− ToJSON,
58− Value (Object),
59− eitherDecode,
57+ eitherDecode, object, pairs,
60− object,
61− pairs,
62− parseJSON,
63− toEncoding,
58+ parseJSON, toEncoding, toJSON,
64− toJSON,
65− withObject,
59+ withObject, withText, (.:), (.:?),
66− withText,
67− (.:),
68− (.:?),
69− (.=),
60+ (.=))
70− )
71−import qualified Data.Aeson as A
61+import qualified Data.Aeson as A
72−import Data.Aeson.Types (typeMismatch)
62+import Data.Aeson.Types (typeMismatch)
73−import qualified Data.ByteString as B
63+import qualified Data.ByteString as B
74−import qualified Data.ByteString.Lazy as LBS
64+import qualified Data.ByteString.Lazy as LBS
75−import qualified Data.Text as T
65+import qualified Data.Text as T
76−import Data.Time (NominalDiffTime, getCurrentTime)
66+import Data.Time (NominalDiffTime, getCurrentTime)
77−import Lib.Prelude
78−import Network.HTTP.Conduit (Response)
67+import Network.HTTP.Conduit (Response)
79−import qualified Network.HTTP.Conduit as NC
68+import qualified Network.HTTP.Conduit as NC
80−import qualified Network.HTTP.Types as HT
69+import qualified Network.HTTP.Types as HT
81−import Network.HTTP.Types.Header (hHost)
70+import Network.HTTP.Types.Header (hHost)
71+
72+import Lib.Prelude
73+
82−import Network.Minio.APICommon
74+import Network.Minio.APICommon
83−import Network.Minio.Credentials
84−import Network.Minio.Data
75+import Network.Minio.Data
85−import Network.Minio.Errors
76+import Network.Minio.Errors
86−import Network.Minio.Sign.V4
77+import Network.Minio.Sign.V4
87−import Network.Minio.Utils
78+import Network.Minio.Utils
88
79
89data DriveInfo = DriveInfo
80data DriveInfo = DriveInfo
90− { diUuid :: Text,
81+ { diUuid :: Text
91− diEndpoint :: Text,
82+ , diEndpoint :: Text
92− diState :: Text
83+ , diState :: Text
93− }
94− deriving stock (Show, Eq)
84+ } deriving (Eq, Show)
95
85
96instance FromJSON DriveInfo where
86instance FromJSON DriveInfo where
97− parseJSON = withObject "DriveInfo" $ \v ->
87+ parseJSON = withObject "DriveInfo" $ \v -> DriveInfo
98− DriveInfo
99− <$> v
100− .: "uuid"
88+ <$> v .: "uuid"
101− <*> v
102− .: "endpoint"
89+ <*> v .: "endpoint"
103− <*> v
104− .: "state"
90+ <*> v .: "state"
105
91
106data StorageClass = StorageClass
92data StorageClass = StorageClass
107− { scParity :: Int,
93+ { scParity :: Int
108− scData :: Int
94+ , scData :: Int
109− }
110− deriving stock (Show, Eq)
95+ } deriving (Eq, Show)
111
96
112data ErasureInfo = ErasureInfo
97data ErasureInfo = ErasureInfo
113− { eiOnlineDisks :: Int,
98+ { eiOnlineDisks :: Int
114− eiOfflineDisks :: Int,
99+ , eiOfflineDisks :: Int
115− eiStandard :: StorageClass,
100+ , eiStandard :: StorageClass
116− eiReducedRedundancy :: StorageClass,
101+ , eiReducedRedundancy :: StorageClass
117− eiSets :: [[DriveInfo]]
102+ , eiSets :: [[DriveInfo]]
118− }
119− deriving stock (Show, Eq)
103+ } deriving (Eq, Show)
120
104
121instance FromJSON ErasureInfo where
105instance FromJSON ErasureInfo where
122− parseJSON = withObject "ErasureInfo" $ \v -> do
106+ parseJSON = withObject "ErasureInfo" $ \v -> do
123− onlineDisks <- v .: "OnlineDisks"
107+ onlineDisks <- v .: "OnlineDisks"
124− offlineDisks <- v .: "OfflineDisks"
108+ offlineDisks <- v .: "OfflineDisks"
125− stdClass <-
126− StorageClass
109+ stdClass <- StorageClass
127− <$> v
128− .: "StandardSCData"
110+ <$> v .: "StandardSCData"
129− <*> v
130− .: "StandardSCParity"
111+ <*> v .: "StandardSCParity"
131− rrClass <-
132− StorageClass
112+ rrClass <- StorageClass
133− <$> v
134− .: "RRSCData"
113+ <$> v .: "RRSCData"
135− <*> v
136− .: "RRSCParity"
114+ <*> v .: "RRSCParity"
137− sets <- v .: "Sets"
115+ sets <- v .: "Sets"
138− return $ ErasureInfo onlineDisks offlineDisks stdClass rrClass sets
116+ return $ ErasureInfo onlineDisks offlineDisks stdClass rrClass sets
139−
117+
140−data Backend
118+data Backend = BackendFS
141− = BackendFS
142− | BackendErasure ErasureInfo
119+ | BackendErasure ErasureInfo
143− deriving stock (Show, Eq)
120+ deriving (Eq, Show)
144
121
145instance FromJSON Backend where
122instance FromJSON Backend where
146− parseJSON = withObject "Backend" $ \v -> do
123+ parseJSON = withObject "Backend" $ \v -> do
147− typ <- v .: "Type"
124+ typ <- v .: "Type"
148− case typ :: Int of
125+ case typ :: Int of
149− 1 -> return BackendFS
126+ 1 -> return BackendFS
150− 2 -> BackendErasure <$> parseJSON (Object v)
127+ 2 -> BackendErasure <$> parseJSON (Object v)
151− _ -> typeMismatch "BackendType" (Object v)
128+ _ -> typeMismatch "BackendType" (Object v)
152
129
153data ConnStats = ConnStats
130data ConnStats = ConnStats
154− { csTransferred :: Int64,
131+ { csTransferred :: Int64
155− csReceived :: Int64
132+ , csReceived :: Int64
156− }
157− deriving stock (Show, Eq)
133+ } deriving (Eq, Show)
158
134
159instance FromJSON ConnStats where
135instance FromJSON ConnStats where
160− parseJSON = withObject "ConnStats" $ \v ->
136+ parseJSON = withObject "ConnStats" $ \v -> ConnStats
161− ConnStats
162− <$> v
163− .: "transferred"
137+ <$> v .: "transferred"
164− <*> v
165− .: "received"
138+ <*> v .: "received"
166
139
167data ServerProps = ServerProps
140data ServerProps = ServerProps
168− { spUptime :: NominalDiffTime,
141+ { spUptime :: NominalDiffTime
169− spVersion :: Text,
142+ , spVersion :: Text
170− spCommitId :: Text,
143+ , spCommitId :: Text
171− spRegion :: Text,
144+ , spRegion :: Text
172− spSqsArns :: [Text]
145+ , spSqsArns :: [Text]
173− }
174− deriving stock (Show, Eq)
146+ } deriving (Eq, Show)
175
147
176instance FromJSON ServerProps where
148instance FromJSON ServerProps where
177− parseJSON = withObject "SIServer" $ \v -> do
149+ parseJSON = withObject "SIServer" $ \v -> do
178− uptimeNs <- v .: "uptime"
150+ uptimeNs <- v .: "uptime"
179− let uptime = uptimeNs / 1e9
151+ let uptime = uptimeNs / 1e9
180− ver <- v .: "version"
152+ ver <- v .: "version"
181− commitId <- v .: "commitID"
153+ commitId <- v .: "commitID"
182− region <- v .: "region"
154+ region <- v .: "region"
183− arn <- v .: "sqsARN"
155+ arn <- v .: "sqsARN"
184− return $ ServerProps uptime ver commitId region arn
156+ return $ ServerProps uptime ver commitId region arn
185
157
186data StorageInfo = StorageInfo
158data StorageInfo = StorageInfo
187− { siUsed :: Int64,
159+ { siUsed :: Int64
188− siBackend :: Backend
160+ , siBackend :: Backend
189− }
190− deriving stock (Show, Eq)
161+ } deriving (Eq, Show)
191
162
192instance FromJSON StorageInfo where
163instance FromJSON StorageInfo where
193− parseJSON = withObject "StorageInfo" $ \v ->
164+ parseJSON = withObject "StorageInfo" $ \v -> StorageInfo
194− StorageInfo
195− <$> v
196− .: "Used"
165+ <$> v .: "Used"
197− <*> v
198− .: "Backend"
166+ <*> v .: "Backend"
199
167
200data CountNAvgTime = CountNAvgTime
168data CountNAvgTime = CountNAvgTime
201− { caCount :: Int64,
169+ { caCount :: Int64
202− caAvgDuration :: Text
170+ , caAvgDuration :: Text
203− }
204− deriving stock (Show, Eq)
171+ } deriving (Eq, Show)
205
172
206instance FromJSON CountNAvgTime where
173instance FromJSON CountNAvgTime where
207− parseJSON = withObject "CountNAvgTime" $ \v ->
174+ parseJSON = withObject "CountNAvgTime" $ \v -> CountNAvgTime
208− CountNAvgTime
209− <$> v
210− .: "count"
175+ <$> v .: "count"
211− <*> v
212− .: "avgDuration"
176+ <*> v .: "avgDuration"
213
177
214data HttpStats = HttpStats
178data HttpStats = HttpStats
215− { hsTotalHeads :: CountNAvgTime,
179+ { hsTotalHeads :: CountNAvgTime
216− hsSuccessHeads :: CountNAvgTime,
180+ , hsSuccessHeads :: CountNAvgTime
217− hsTotalGets :: CountNAvgTime,
181+ , hsTotalGets :: CountNAvgTime
218− hsSuccessGets :: CountNAvgTime,
182+ , hsSuccessGets :: CountNAvgTime
219− hsTotalPuts :: CountNAvgTime,
183+ , hsTotalPuts :: CountNAvgTime
220− hsSuccessPuts :: CountNAvgTime,
184+ , hsSuccessPuts :: CountNAvgTime
221− hsTotalPosts :: CountNAvgTime,
185+ , hsTotalPosts :: CountNAvgTime
222− hsSuccessPosts :: CountNAvgTime,
186+ , hsSuccessPosts :: CountNAvgTime
223− hsTotalDeletes :: CountNAvgTime,
187+ , hsTotalDeletes :: CountNAvgTime
224− hsSuccessDeletes :: CountNAvgTime
188+ , hsSuccessDeletes :: CountNAvgTime
225− }
226− deriving stock (Show, Eq)
189+ } deriving (Eq, Show)
227
190
228instance FromJSON HttpStats where
191instance FromJSON HttpStats where
229− parseJSON = withObject "HttpStats" $ \v ->
192+ parseJSON = withObject "HttpStats" $ \v -> HttpStats
230− HttpStats
231− <$> v
232− .: "totalHEADs"
193+ <$> v .: "totalHEADs"
233− <*> v
234− .: "successHEADs"
194+ <*> v .: "successHEADs"
235− <*> v
236− .: "totalGETs"
195+ <*> v .: "totalGETs"
237− <*> v
238− .: "successGETs"
196+ <*> v .: "successGETs"
239− <*> v
240− .: "totalPUTs"
197+ <*> v .: "totalPUTs"
241− <*> v
242− .: "successPUTs"
198+ <*> v .: "successPUTs"
243− <*> v
244− .: "totalPOSTs"
199+ <*> v .: "totalPOSTs"
245− <*> v
246− .: "successPOSTs"
200+ <*> v .: "successPOSTs"
247− <*> v
248− .: "totalDELETEs"
201+ <*> v .: "totalDELETEs"
249− <*> v
250− .: "successDELETEs"
202+ <*> v .: "successDELETEs"
251
203
252data SIData = SIData
204data SIData = SIData
253− { sdStorage :: StorageInfo,
205+ { sdStorage :: StorageInfo
254− sdConnStats :: ConnStats,
206+ , sdConnStats :: ConnStats
255− sdHttpStats :: HttpStats,
207+ , sdHttpStats :: HttpStats
256− sdProps :: ServerProps
208+ , sdProps :: ServerProps
257− }
258− deriving stock (Show, Eq)
209+ } deriving (Eq, Show)
259
210
260instance FromJSON SIData where
211instance FromJSON SIData where
261− parseJSON = withObject "SIData" $ \v ->
212+ parseJSON = withObject "SIData" $ \v -> SIData
262− SIData
263− <$> v
264− .: "storage"
213+ <$> v .: "storage"
265− <*> v
266− .: "network"
214+ <*> v .: "network"
267− <*> v
268− .: "http"
215+ <*> v .: "http"
269− <*> v
270− .: "server"
216+ <*> v .: "server"
271
217
272data ServerInfo = ServerInfo
218data ServerInfo = ServerInfo
273− { siError :: Text,
219+ { siError :: Text
274− siAddr :: Text,
220+ , siAddr :: Text
275− siData :: SIData
221+ , siData :: SIData
276− }
277− deriving stock (Show, Eq)
222+ } deriving (Eq, Show)
278
223
279instance FromJSON ServerInfo where
224instance FromJSON ServerInfo where
280− parseJSON = withObject "ServerInfo" $ \v ->
225+ parseJSON = withObject "ServerInfo" $ \v -> ServerInfo
281− ServerInfo
282− <$> v
283− .: "error"
226+ <$> v .: "error"
284− <*> v
285− .: "addr"
227+ <*> v .: "addr"
286− <*> v
287− .: "data"
228+ <*> v .: "data"
288
229
289data ServerVersion = ServerVersion
230data ServerVersion = ServerVersion
290− { svVersion :: Text,
231+ { svVersion :: Text
291− svCommitId :: Text
232+ , svCommitId :: Text
292− }
293− deriving stock (Show, Eq)
233+ } deriving (Eq, Show)
294
234
295instance FromJSON ServerVersion where
235instance FromJSON ServerVersion where
296− parseJSON = withObject "ServerVersion" $ \v ->
236+ parseJSON = withObject "ServerVersion" $ \v -> ServerVersion
297− ServerVersion
298− <$> v
299− .: "version"
237+ <$> v .: "version"
300− <*> v
301− .: "commitID"
238+ <*> v .: "commitID"
302
239
303data ServiceStatus = ServiceStatus
240data ServiceStatus = ServiceStatus
304− { ssVersion :: ServerVersion,
241+ { ssVersion :: ServerVersion
305− ssUptime :: NominalDiffTime
242+ , ssUptime :: NominalDiffTime
306− }
307− deriving stock (Show, Eq)
243+ } deriving (Eq, Show)
308
244
309instance FromJSON ServiceStatus where
245instance FromJSON ServiceStatus where
310− parseJSON = withObject "ServiceStatus" $ \v -> do
246+ parseJSON = withObject "ServiceStatus" $ \v -> do
311− serverVersion <- v .: "serverVersion"
247+ serverVersion <- v .: "serverVersion"
312− uptimeNs <- v .: "uptime"
248+ uptimeNs <- v .: "uptime"
313− let uptime = uptimeNs / 1e9
249+ let uptime = uptimeNs / 1e9
314− return $ ServiceStatus serverVersion uptime
250+ return $ ServiceStatus serverVersion uptime
315
251
316−data ServiceAction
317− = ServiceActionRestart
252+data ServiceAction = ServiceActionRestart
318− | ServiceActionStop
253+ | ServiceActionStop
319− deriving stock (Show, Eq)
254+ deriving (Eq, Show)
320
255
321instance ToJSON ServiceAction where
256instance ToJSON ServiceAction where
322− toJSON a = object ["action" .= serviceActionToText a]
257+ toJSON a = object [ "action" .= serviceActionToText a ]
323
258
324serviceActionToText :: ServiceAction -> Text
259serviceActionToText :: ServiceAction -> Text
325serviceActionToText a = case a of
260serviceActionToText a = case a of
326 ServiceActionRestart -> "restart"
261 ServiceActionRestart -> "restart"
327− ServiceActionStop -> "stop"
262+ ServiceActionStop -> "stop"
328
263
329adminPath :: ByteString
264adminPath :: ByteString
330adminPath = "/minio/admin"
265adminPath = "/minio/admin"
331
266
332data HealStartResp = HealStartResp
267data HealStartResp = HealStartResp
333− { hsrClientToken :: Text,
268+ { hsrClientToken :: Text
334− hsrClientAddr :: Text,
269+ , hsrClientAddr :: Text
335− hsrStartTime :: UTCTime
270+ , hsrStartTime :: UTCTime
336− }
337− deriving stock (Show, Eq)
271+ } deriving (Eq, Show)
338
272
339instance FromJSON HealStartResp where
273instance FromJSON HealStartResp where
340− parseJSON = withObject "HealStartResp" $ \v ->
274+ parseJSON = withObject "HealStartResp" $ \v -> HealStartResp
341− HealStartResp
342− <$> v
343− .: "clientToken"
275+ <$> v .: "clientToken"
344− <*> v
345− .: "clientAddress"
276+ <*> v .: "clientAddress"
346− <*> v
347− .: "startTime"
277+ <*> v .: "startTime"
348
278
349data HealOpts = HealOpts
279data HealOpts = HealOpts
350− { hoRecursive :: Bool,
280+ { hoRecursive :: Bool
351− hoDryRun :: Bool
281+ , hoDryRun :: Bool
352− }
353− deriving stock (Show, Eq)
282+ } deriving (Eq, Show)
354
283
355instance ToJSON HealOpts where
284instance ToJSON HealOpts where
356 toJSON (HealOpts r d) =
285 toJSON (HealOpts r d) =
@@ -359,259 +288,197 @@ instance ToJSON HealOpts where
359 pairs ("recursive" .= r <> "dryRun" .= d)
288 pairs ("recursive" .= r <> "dryRun" .= d)
360
289
361instance FromJSON HealOpts where
290instance FromJSON HealOpts where
362− parseJSON = withObject "HealOpts" $ \v ->
291+ parseJSON = withObject "HealOpts" $ \v -> HealOpts
363− HealOpts
364− <$> v
365− .: "recursive"
292+ <$> v .: "recursive"
366− <*> v
367− .: "dryRun"
293+ <*> v .: "dryRun"
368−
294+
369−data HealItemType
295+data HealItemType = HealItemMetadata
370− = HealItemMetadata
371− | HealItemBucket
296+ | HealItemBucket
372− | HealItemBucketMetadata
297+ | HealItemBucketMetadata
373− | HealItemObject
298+ | HealItemObject
374− deriving stock (Show, Eq)
299+ deriving (Eq, Show)
375
300
376instance FromJSON HealItemType where
301instance FromJSON HealItemType where
377− parseJSON = withText "HealItemType" $ \v -> case v of
302+ parseJSON = withText "HealItemType" $ \v -> case v of
378− "metadata" -> return HealItemMetadata
303+ "metadata" -> return HealItemMetadata
379− "bucket" -> return HealItemBucket
304+ "bucket" -> return HealItemBucket
380− "object" -> return HealItemObject
305+ "object" -> return HealItemObject
381− "bucket-metadata" -> return HealItemBucketMetadata
306+ "bucket-metadata" -> return HealItemBucketMetadata
382− _ -> typeMismatch "HealItemType" (A.String v)
307+ _ -> typeMismatch "HealItemType" (A.String v)
383
308
384data NodeSummary = NodeSummary
309data NodeSummary = NodeSummary
385− { nsName :: Text,
310+ { nsName :: Text
386− nsErrSet :: Bool,
311+ , nsErrSet :: Bool
387− nsErrMessage :: Text
312+ , nsErrMessage :: Text
388− }
389− deriving stock (Show, Eq)
313+ } deriving (Eq, Show)
390
314
391instance FromJSON NodeSummary where
315instance FromJSON NodeSummary where
392− parseJSON = withObject "NodeSummary" $ \v ->
316+ parseJSON = withObject "NodeSummary" $ \v -> NodeSummary
393− NodeSummary
394− <$> v
395− .: "name"
317+ <$> v .: "name"
396− <*> v
397− .: "errSet"
318+ <*> v .: "errSet"
398− <*> v
399− .: "errMsg"
319+ <*> v .: "errMsg"
400
320
401data SetConfigResult = SetConfigResult
321data SetConfigResult = SetConfigResult
402− { scrStatus :: Bool,
322+ { scrStatus :: Bool
403− scrNodeSummary :: [NodeSummary]
323+ , scrNodeSummary :: [NodeSummary]
404− }
405− deriving stock (Show, Eq)
324+ } deriving (Eq, Show)
406
325
407instance FromJSON SetConfigResult where
326instance FromJSON SetConfigResult where
408− parseJSON = withObject "SetConfigResult" $ \v ->
327+ parseJSON = withObject "SetConfigResult" $ \v -> SetConfigResult
409− SetConfigResult
410− <$> v
411− .: "status"
328+ <$> v .: "status"
412− <*> v
413− .: "nodeResults"
329+ <*> v .: "nodeResults"
414
330
415data HealResultItem = HealResultItem
331data HealResultItem = HealResultItem
416− { hriResultIdx :: Int,
332+ { hriResultIdx :: Int
417− hriType :: HealItemType,
333+ , hriType :: HealItemType
418− hriBucket :: Bucket,
334+ , hriBucket :: Bucket
419− hriObject :: Object,
335+ , hriObject :: Object
420− hriDetail :: Text,
336+ , hriDetail :: Text
421− hriParityBlocks :: Maybe Int,
337+ , hriParityBlocks :: Maybe Int
422− hriDataBlocks :: Maybe Int,
338+ , hriDataBlocks :: Maybe Int
423− hriDiskCount :: Int,
339+ , hriDiskCount :: Int
424− hriSetCount :: Int,
340+ , hriSetCount :: Int
425− hriObjectSize :: Int,
341+ , hriObjectSize :: Int
426− hriBefore :: [DriveInfo],
342+ , hriBefore :: [DriveInfo]
427− hriAfter :: [DriveInfo]
343+ , hriAfter :: [DriveInfo]
428− }
429− deriving stock (Show, Eq)
344+ } deriving (Eq, Show)
430
345
431instance FromJSON HealResultItem where
346instance FromJSON HealResultItem where
432− parseJSON = withObject "HealResultItem" $ \v ->
347+ parseJSON = withObject "HealResultItem" $ \v -> HealResultItem
433− HealResultItem
434− <$> v
435− .: "resultId"
348+ <$> v .: "resultId"
436− <*> v
437− .: "type"
349+ <*> v .: "type"
438− <*> v
439− .: "bucket"
350+ <*> v .: "bucket"
440− <*> v
441− .: "object"
351+ <*> v .: "object"
442− <*> v
443− .: "detail"
352+ <*> v .: "detail"
444− <*> v
445− .:? "parityBlocks"
353+ <*> v .:? "parityBlocks"
446− <*> v
447− .:? "dataBlocks"
354+ <*> v .:? "dataBlocks"
448− <*> v
449− .: "diskCount"
355+ <*> v .: "diskCount"
450− <*> v
451− .: "setCount"
356+ <*> v .: "setCount"
452− <*> v
453− .: "objectSize"
357+ <*> v .: "objectSize"
454− <*> ( do
455− before <- v .: "before"
358+ <*> (do before <- v .: "before"
456− before .: "drives"
359+ before .: "drives")
457− )
458− <*> ( do
459− after <- v .: "after"
360+ <*> (do after <- v .: "after"
460− after .: "drives"
361+ after .: "drives")
461− )
462
362
463data HealStatus = HealStatus
363data HealStatus = HealStatus
464− { hsSummary :: Text,
364+ { hsSummary :: Text
465− hsStartTime :: UTCTime,
365+ , hsStartTime :: UTCTime
466− hsSettings :: HealOpts,
366+ , hsSettings :: HealOpts
467− hsNumDisks :: Int,
367+ , hsNumDisks :: Int
468− hsFailureDetail :: Maybe Text,
368+ , hsFailureDetail :: Maybe Text
469− hsItems :: Maybe [HealResultItem]
369+ , hsItems :: Maybe [HealResultItem]
470− }
471− deriving stock (Show, Eq)
370+ } deriving (Eq, Show)
472
371
473instance FromJSON HealStatus where
372instance FromJSON HealStatus where
474− parseJSON = withObject "HealStatus" $ \v ->
373+ parseJSON = withObject "HealStatus" $ \v -> HealStatus
475− HealStatus
476− <$> v
477− .: "Summary"
374+ <$> v .: "Summary"
478− <*> v
479− .: "StartTime"
375+ <*> v .: "StartTime"
480− <*> v
481− .: "Settings"
376+ <*> v .: "Settings"
482− <*> v
483− .: "NumDisks"
377+ <*> v .: "NumDisks"
484− <*> v
485− .:? "Detail"
378+ <*> v .:? "Detail"
486− <*> v
487− .: "Items"
379+ <*> v .: "Items"
488
380
489healPath :: Maybe Bucket -> Maybe Text -> ByteString
381healPath :: Maybe Bucket -> Maybe Text -> ByteString
490healPath bucket prefix = do
382healPath bucket prefix = do
491− if isJust bucket
383+ if (isJust bucket)
492− then
493− encodeUtf8 $
494− "v1/heal/"
495− <> fromMaybe "" bucket
384+ then encodeUtf8 $ "v1/heal/" <> fromMaybe "" bucket <> "/"
496− <> "/"
497− <> fromMaybe "" prefix
385+ <> fromMaybe "" prefix
498− else encodeUtf8 ("v1/heal/" :: Text)
386+ else encodeUtf8 $ "v1/heal/"
499
387
500-- | Get server version and uptime.
388-- | Get server version and uptime.
501serviceStatus :: Minio ServiceStatus
389serviceStatus :: Minio ServiceStatus
502serviceStatus = do
390serviceStatus = do
503− rsp <-
504− executeAdminRequest
505− AdminReqInfo
506− { ariMethod = HT.methodGet,
391+ rsp <- executeAdminRequest AdminReqInfo { ariMethod = HT.methodGet
507− ariPayload = PayloadBS B.empty,
392+ , ariPayload = PayloadBS B.empty
508− ariPayloadHash = Nothing,
393+ , ariPayloadHash = Nothing
509− ariPath = "v1/service",
394+ , ariPath = "v1/service"
510− ariHeaders = [],
395+ , ariHeaders = []
511− ariQueryParams = []
396+ , ariQueryParams = []
512− }
397+ }
513−
398+
514− let rspBS = NC.responseBody rsp
399+ let rspBS = NC.responseBody rsp
515− case eitherDecode rspBS of
400+ case eitherDecode rspBS of
516− Right ss -> return ss
401+ Right ss -> return ss
517− Left err -> throwIO $ MErrVJsonParse $ T.pack err
402+ Left err -> throwIO $ MErrVJsonParse $ T.pack err
518
403
519-- | Send service restart or stop action to MinIO server.
404-- | Send service restart or stop action to MinIO server.
520serviceSendAction :: ServiceAction -> Minio ()
405serviceSendAction :: ServiceAction -> Minio ()
521serviceSendAction action = do
406serviceSendAction action = do
522− let payload = PayloadBS $ LBS.toStrict $ A.encode action
407+ let payload = PayloadBS $ LBS.toStrict $ A.encode action
523− void $
524− executeAdminRequest
525− AdminReqInfo
526− { ariMethod = HT.methodPost,
408+ void $ executeAdminRequest AdminReqInfo { ariMethod = HT.methodPost
527− ariPayload = payload,
409+ , ariPayload = payload
528− ariPayloadHash = Nothing,
410+ , ariPayloadHash = Nothing
529− ariPath = "v1/service",
411+ , ariPath = "v1/service"
530− ariHeaders = [],
412+ , ariHeaders = []
531− ariQueryParams = []
413+ , ariQueryParams = []
532− }
414+ }
533
415
534-- | Get the current config file from server.
416-- | Get the current config file from server.
535getConfig :: Minio ByteString
417getConfig :: Minio ByteString
536getConfig = do
418getConfig = do
537− rsp <-
538− executeAdminRequest
539− AdminReqInfo
540− { ariMethod = HT.methodGet,
419+ rsp <- executeAdminRequest AdminReqInfo { ariMethod = HT.methodGet
541− ariPayload = PayloadBS B.empty,
420+ , ariPayload = PayloadBS B.empty
542− ariPayloadHash = Nothing,
421+ , ariPayloadHash = Nothing
543− ariPath = "v1/config",
422+ , ariPath = "v1/config"
544− ariHeaders = [],
423+ , ariHeaders = []
545− ariQueryParams = []
424+ , ariQueryParams = []
546− }
425+ }
547− return $ LBS.toStrict $ NC.responseBody rsp
426+ return $ LBS.toStrict $ NC.responseBody rsp
548
427
549-- | Set a new config to the server.
428-- | Set a new config to the server.
550setConfig :: ByteString -> Minio SetConfigResult
429setConfig :: ByteString -> Minio SetConfigResult
551setConfig config = do
430setConfig config = do
552− rsp <-
553− executeAdminRequest
554− AdminReqInfo
555− { ariMethod = HT.methodPut,
431+ rsp <- executeAdminRequest AdminReqInfo { ariMethod = HT.methodPut
556− ariPayload = PayloadBS config,
432+ , ariPayload = PayloadBS config
557− ariPayloadHash = Nothing,
433+ , ariPayloadHash = Nothing
558− ariPath = "v1/config",
434+ , ariPath = "v1/config"
559− ariHeaders = [],
435+ , ariHeaders = []
560− ariQueryParams = []
436+ , ariQueryParams = []
561− }
437+ }
562−
438+
563− let rspBS = NC.responseBody rsp
439+ let rspBS = NC.responseBody rsp
564− case eitherDecode rspBS of
440+ case eitherDecode rspBS of
565− Right scr -> return scr
441+ Right scr -> return scr
566− Left err -> throwIO $ MErrVJsonParse $ T.pack err
442+ Left err -> throwIO $ MErrVJsonParse $ T.pack err
567
443
568-- | Get the progress of currently running heal task, this API should be
444-- | Get the progress of currently running heal task, this API should be
569-- invoked right after `startHeal`. `token` is obtained after `startHeal`
445-- invoked right after `startHeal`. `token` is obtained after `startHeal`
570-- which should be used to get the heal status.
446-- which should be used to get the heal status.
571getHealStatus :: Maybe Bucket -> Maybe Text -> Text -> Minio HealStatus
447getHealStatus :: Maybe Bucket -> Maybe Text -> Text -> Minio HealStatus
572getHealStatus bucket prefix token = do
448getHealStatus bucket prefix token = do
573− when (isNothing bucket && isJust prefix) $ throwIO MErrVInvalidHealPath
449+ when (isNothing bucket && isJust prefix) $ throwIO MErrVInvalidHealPath
574− let qparams = HT.queryTextToQuery [("clientToken", Just token)]
450+ let qparams = HT.queryTextToQuery [("clientToken", Just token)]
575− rsp <-
576− executeAdminRequest
577− AdminReqInfo
578− { ariMethod = HT.methodPost,
451+ rsp <- executeAdminRequest AdminReqInfo { ariMethod = HT.methodPost
579− ariPayload = PayloadBS B.empty,
452+ , ariPayload = PayloadBS B.empty
580− ariPayloadHash = Nothing,
453+ , ariPayloadHash = Nothing
581− ariPath = healPath bucket prefix,
454+ , ariPath = healPath bucket prefix
582− ariHeaders = [],
455+ , ariHeaders = []
583− ariQueryParams = qparams
456+ , ariQueryParams = qparams
584− }
457+ }
585− let rspBS = NC.responseBody rsp
458+ let rspBS = NC.responseBody rsp
586− case eitherDecode rspBS of
459+ case eitherDecode rspBS of
587− Right hs -> return hs
460+ Right hs -> return hs
588− Left err -> throwIO $ MErrVJsonParse $ T.pack err
461+ Left err -> throwIO $ MErrVJsonParse $ T.pack err
589
462
590doHeal :: Maybe Bucket -> Maybe Text -> HealOpts -> Bool -> Minio HealStartResp
463doHeal :: Maybe Bucket -> Maybe Text -> HealOpts -> Bool -> Minio HealStartResp
591doHeal bucket prefix opts forceStart = do
464doHeal bucket prefix opts forceStart = do
592− when (isNothing bucket && isJust prefix) $ throwIO MErrVInvalidHealPath
465+ when (isNothing bucket && isJust prefix) $ throwIO MErrVInvalidHealPath
593− let payload = PayloadBS $ LBS.toStrict $ A.encode opts
466+ let payload = PayloadBS $ LBS.toStrict $ A.encode opts
594− let qparams =
595− bool
596− []
597− (HT.queryTextToQuery [("forceStart", Just "true")])
467+ let qparams = bool [] (HT.queryTextToQuery [("forceStart", Just "true")])
598− forceStart
468+ forceStart
599−
469+
600− rsp <-
601− executeAdminRequest
602− AdminReqInfo
603− { ariMethod = HT.methodPost,
470+ rsp <- executeAdminRequest AdminReqInfo { ariMethod = HT.methodPost
604− ariPayload = payload,
471+ , ariPayload = payload
605− ariPayloadHash = Nothing,
472+ , ariPayloadHash = Nothing
606− ariPath = healPath bucket prefix,
473+ , ariPath = healPath bucket prefix
607− ariHeaders = [],
474+ , ariHeaders = []
608− ariQueryParams = qparams
475+ , ariQueryParams = qparams
609− }
476+ }
610−
477+
611− let rspBS = NC.responseBody rsp
478+ let rspBS = NC.responseBody rsp
612− case eitherDecode rspBS of
479+ case eitherDecode rspBS of
613− Right hsr -> return hsr
480+ Right hsr -> return hsr
614− Left err -> throwIO $ MErrVJsonParse $ T.pack err
481+ Left err -> throwIO $ MErrVJsonParse $ T.pack err
615
482
616-- | Start a heal sequence that scans data under given (possible empty)
483-- | Start a heal sequence that scans data under given (possible empty)
617-- `bucket` and `prefix`. The `recursive` bool turns on recursive
484-- `bucket` and `prefix`. The `recursive` bool turns on recursive
@@ -633,79 +500,60 @@ forceStartHeal bucket prefix opts = doHeal bucket prefix opts True
633-- properties, storage information, network statistics, etc.
500-- properties, storage information, network statistics, etc.
634getServerInfo :: Minio [ServerInfo]
501getServerInfo :: Minio [ServerInfo]
635getServerInfo = do
502getServerInfo = do
636− rsp <-
637− executeAdminRequest
638− AdminReqInfo
639− { ariMethod = HT.methodGet,
503+ rsp <- executeAdminRequest AdminReqInfo { ariMethod = HT.methodGet
640− ariPayload = PayloadBS B.empty,
504+ , ariPayload = PayloadBS B.empty
641− ariPayloadHash = Nothing,
505+ , ariPayloadHash = Nothing
642− ariPath = "v1/info",
506+ , ariPath = "v1/info"
643− ariHeaders = [],
507+ , ariHeaders = []
644− ariQueryParams = []
508+ , ariQueryParams = []
645− }
509+ }
646− let rspBS = NC.responseBody rsp
510+ let rspBS = NC.responseBody rsp
647− case eitherDecode rspBS of
511+ case eitherDecode rspBS of
648− Right si -> return si
512+ Right si -> return si
649− Left err -> throwIO $ MErrVJsonParse $ T.pack err
513+ Left err -> throwIO $ MErrVJsonParse $ T.pack err
650
514
651executeAdminRequest :: AdminReqInfo -> Minio (Response LByteString)
515executeAdminRequest :: AdminReqInfo -> Minio (Response LByteString)
652executeAdminRequest ari = do
516executeAdminRequest ari = do
653− req <- buildAdminRequest ari
517+ req <- buildAdminRequest ari
654− mgr <- asks mcConnManager
518+ mgr <- asks mcConnManager
655− httpLbs req mgr
519+ httpLbs req mgr
656
520
657buildAdminRequest :: AdminReqInfo -> Minio NC.Request
521buildAdminRequest :: AdminReqInfo -> Minio NC.Request
658buildAdminRequest areq = do
522buildAdminRequest areq = do
659− ci <- asks mcConnInfo
523+ ci <- asks mcConnInfo
660− sha256Hash <-
661− if connectIsSecure ci
524+ sha256Hash <- if | connectIsSecure ci ->
662− then -- if secure connection
525+ -- if secure connection
663− return "UNSIGNED-PAYLOAD"
526+ return "UNSIGNED-PAYLOAD"
527+
664− else -- otherwise compute sha256
528+ -- otherwise compute sha256
665− getPayloadSHA256Hash (ariPayload areq)
529+ | otherwise -> getPayloadSHA256Hash (ariPayload areq)
666−
530+
667− timeStamp <- liftIO getCurrentTime
531+ timeStamp <- liftIO getCurrentTime
668−
532+
669− mgr <- asks mcConnManager
670− cv <- liftIO $ getCredential (connectCreds ci) (getEndpoint ci) mgr
671−
672− let hostHeader = (hHost, getHostAddr ci)
533+ let hostHeader = (hHost, getHostAddr ci)
673− newAreq =
674− areq
675− { ariPayloadHash = Just sha256Hash,
534+ newAreq = areq { ariPayloadHash = Just sha256Hash
676− ariHeaders =
535+ , ariHeaders = hostHeader
677− hostHeader
678− : sha256Header sha256Hash
536+ : sha256Header sha256Hash
679− : ariHeaders areq
537+ : ariHeaders areq
680− }
538+ }
681− signReq = toRequest ci newAreq
539+ signReq = toRequest ci newAreq
682− sp =
540+ sp = SignParams (connectAccessKey ci) (connectSecretKey ci)
683− SignParams
684− (coerce $ cvAccessKey cv)
685− (coerce $ cvSecretKey cv)
686− (coerce $ cvSessionToken cv)
687− ServiceS3
688− timeStamp
689− Nothing
690− Nothing
691− (ariPayloadHash newAreq)
541+ timeStamp Nothing Nothing (ariPayloadHash newAreq)
692− signHeaders = signV4 sp signReq
542+ signHeaders = signV4 sp signReq
693−
543+
694− -- Update signReq with Authorization header containing v4 signature
544+ -- Update signReq with Authorization header containing v4 signature
695− return
696− signReq
545+ return signReq {
697− { NC.requestHeaders = ariHeaders newAreq ++ signHeaders
546+ NC.requestHeaders = ariHeaders newAreq ++ mkHeaderFromPairs signHeaders
698− }
547+ }
699 where
548 where
700 toRequest :: ConnectInfo -> AdminReqInfo -> NC.Request
549 toRequest :: ConnectInfo -> AdminReqInfo -> NC.Request
701− toRequest ci aReq =
702− NC.defaultRequest
550+ toRequest ci aReq = NC.defaultRequest
703− { NC.method = ariMethod aReq,
551+ { NC.method = ariMethod aReq
704− NC.secure = connectIsSecure ci,
552+ , NC.secure = connectIsSecure ci
705− NC.host = encodeUtf8 $ connectHost ci,
553+ , NC.host = encodeUtf8 $ connectHost ci
706− NC.port = connectPort ci,
554+ , NC.port = connectPort ci
707− NC.path = B.intercalate "/" [adminPath, ariPath aReq],
555+ , NC.path = B.intercalate "/" [adminPath, ariPath aReq]
708− NC.requestHeaders = ariHeaders aReq,
556+ , NC.requestHeaders = ariHeaders aReq
709− NC.queryString = HT.renderQuery False $ ariQueryParams aReq,
557+ , NC.queryString = HT.renderQuery False $ ariQueryParams aReq
710− NC.requestBody = getRequestBody (ariPayload aReq)
558+ , NC.requestBody = getRequestBody (ariPayload aReq)
711 }
559 }
src/Network/Minio/CopyObject.hs+34−48
@@ -16,19 +16,19 @@
16
16
17module Network.Minio.CopyObject where
17module Network.Minio.CopyObject where
18
18
19−import qualified Data.List as List
19+import qualified Data.List as List
20+
20−import Lib.Prelude
21+import Lib.Prelude
22+
21−import Network.Minio.Data
23+import Network.Minio.Data
22−import Network.Minio.Errors
24+import Network.Minio.Errors
23−import Network.Minio.S3API
25+import Network.Minio.S3API
24−import Network.Minio.Utils
26+import Network.Minio.Utils
27+
25
28
26-- | Copy an object using single or multipart copy strategy.
29-- | Copy an object using single or multipart copy strategy.
27−copyObjectInternal ::
30+copyObjectInternal :: Bucket -> Object -> SourceInfo
28− Bucket ->
29− Object ->
30− SourceInfo ->
31− Minio ETag
31+ -> Minio ETag
32copyObjectInternal b' o srcInfo = do
32copyObjectInternal b' o srcInfo = do
33 let sBucket = srcBucket srcInfo
33 let sBucket = srcBucket srcInfo
34 sObject = srcObject srcInfo
34 sObject = srcObject srcInfo
@@ -43,33 +43,27 @@ copyObjectInternal b' o srcInfo = do
43 startOffset = fst range
43 startOffset = fst range
44 endOffset = snd range
44 endOffset = snd range
45
45
46− when
47− ( isJust rangeMay
46+ when (isJust rangeMay &&
48− && ( (startOffset < 0)
49− || (endOffset < startOffset)
47+ or [startOffset < 0, endOffset < startOffset,
50− || (endOffset >= srcSize)
48+ endOffset >= fromIntegral srcSize]) $
51− )
52− )
53− $ throwIO
54− $ MErrVInvalidSrcObjByteRange range
49+ throwIO $ MErrVInvalidSrcObjByteRange range
55
50
56 -- 1. If sz > 64MiB (minPartSize) use multipart copy, OR
51 -- 1. If sz > 64MiB (minPartSize) use multipart copy, OR
57 -- 2. If startOffset /= 0 use multipart copy
52 -- 2. If startOffset /= 0 use multipart copy
58− let destSize =
53+ let destSize = (\(a, b) -> b - a + 1 ) $
59− (\(a, b) -> b - a + 1) $
60− maybe (0, srcSize - 1) identity rangeMay
54+ maybe (0, srcSize - 1) identity rangeMay
61
55
62 if destSize > minPartSize || (endOffset - startOffset + 1 /= srcSize)
56 if destSize > minPartSize || (endOffset - startOffset + 1 /= srcSize)
63 then multiPartCopyObject b' o srcInfo srcSize
57 then multiPartCopyObject b' o srcInfo srcSize
58+
64− else fst <$> copyObjectSingle b' o srcInfo {srcRange = Nothing} []
59+ else fst <$> copyObjectSingle b' o srcInfo{srcRange = Nothing} []
65
60
66-- | Given the input byte range of the source object, compute the
61-- | Given the input byte range of the source object, compute the
67-- splits for a multipart copy object procedure. Minimum part size
62-- splits for a multipart copy object procedure. Minimum part size
68-- used is minPartSize.
63-- used is minPartSize.
69selectCopyRanges :: (Int64, Int64) -> [(PartNumber, (Int64, Int64))]
64selectCopyRanges :: (Int64, Int64) -> [(PartNumber, (Int64, Int64))]
70−selectCopyRanges (st, end) =
65+selectCopyRanges (st, end) = zip pns $
71− zip pns $
72− zipWith (\x y -> (st + x, st + x + y - 1)) startOffsets partSizes
66+ map (\(x, y) -> (st + x, st + x + y - 1)) $ zip startOffsets partSizes
73 where
67 where
74 size = end - st + 1
68 size = end - st + 1
75 (pns, startOffsets, partSizes) = List.unzip3 $ selectPartSizes size
69 (pns, startOffsets, partSizes) = List.unzip3 $ selectPartSizes size
@@ -77,30 +71,22 @@ selectCopyRanges (st, end) =
77-- | Perform a multipart copy object action. Since we cannot verify
71-- | Perform a multipart copy object action. Since we cannot verify
78-- existing parts based on the source object, there is no resuming
72-- existing parts based on the source object, there is no resuming
79-- copy action support.
73-- copy action support.
80−multiPartCopyObject ::
74+multiPartCopyObject :: Bucket -> Object -> SourceInfo -> Int64
81− Bucket ->
82− Object ->
83− SourceInfo ->
84− Int64 ->
85− Minio ETag
75+ -> Minio ETag
86multiPartCopyObject b o cps srcSize = do
76multiPartCopyObject b o cps srcSize = do
87 uid <- newMultipartUpload b o []
77 uid <- newMultipartUpload b o []
88
78
89− let byteRange = maybe (0, srcSize - 1) identity $ srcRange cps
79+ let byteRange = maybe (0, fromIntegral $ srcSize - 1) identity $ srcRange cps
90 partRanges = selectCopyRanges byteRange
80 partRanges = selectCopyRanges byteRange
91− partSources =
92− map
93− (\(x, (start, end)) -> (x, cps {srcRange = Just (start, end)}))
81+ partSources = map (\(x, (start, end)) -> (x, cps {srcRange = Just (start, end) }))
94− partRanges
82+ partRanges
95− dstInfo = defaultDestinationInfo {dstBucket = b, dstObject = o}
83+ dstInfo = defaultDestinationInfo { dstBucket = b, dstObject = o}
96−
84+
97− copiedParts <-
98− limitedMapConcurrently
85+ copiedParts <- limitedMapConcurrently 10
99− 10
100− ( \(pn, cps') -> do
86+ (\(pn, cps') -> do
101− (etag, _) <- copyObjectPart dstInfo cps' uid pn []
87+ (etag, _) <- copyObjectPart dstInfo cps' uid pn []
102− return (pn, etag)
88+ return (pn, etag)
103− )
89+ )
104− partSources
90+ partSources
105
91
106 completeMultipartUpload b o uid copiedParts
92 completeMultipartUpload b o uid copiedParts
src/Network/Minio/Credentials.hsdeleted−77
@@ -1,77 +0,0 @@
1−--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
3−--
4−-- Licensed under the Apache License, Version 2.0 (the "License");
5−-- you may not use this file except in compliance with the License.
6−-- You may obtain a copy of the License at
7−--
8−-- http://www.apache.org/licenses/LICENSE-2.0
9−--
10−-- Unless required by applicable law or agreed to in writing, software
11−-- distributed under the License is distributed on an "AS IS" BASIS,
12−-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13−-- See the License for the specific language governing permissions and
14−-- limitations under the License.
15−--
16−
17−module Network.Minio.Credentials
18− ( CredentialValue (..),
19− credentialValueText,
20− STSCredentialProvider (..),
21− AccessKey (..),
22− SecretKey (..),
23− SessionToken (..),
24− ExpiryTime (..),
25− STSCredentialStore,
26− initSTSCredential,
27− getSTSCredential,
28− Creds (..),
29− getCredential,
30− Endpoint,
31−
32− -- * STS Assume Role
33− defaultSTSAssumeRoleOptions,
34− STSAssumeRole (..),
35− STSAssumeRoleOptions (..),
36− )
37−where
38−
39−import Data.Time (diffUTCTime, getCurrentTime)
40−import qualified Network.HTTP.Client as NC
41−import Network.Minio.Credentials.AssumeRole
42−import Network.Minio.Credentials.Types
43−import qualified UnliftIO.MVar as M
44−
45−data STSCredentialStore = STSCredentialStore
46− { cachedCredentials :: M.MVar (CredentialValue, ExpiryTime),
47− refreshAction :: Endpoint -> NC.Manager -> IO (CredentialValue, ExpiryTime)
48− }
49−
50−initSTSCredential :: (STSCredentialProvider p) => p -> IO STSCredentialStore
51−initSTSCredential p = do
52− let action = retrieveSTSCredentials p
53− -- start with dummy credential, so that refresh happens for first request.
54− now <- getCurrentTime
55− mvar <- M.newMVar (CredentialValue mempty mempty mempty, coerce now)
56− return $
57− STSCredentialStore
58− { cachedCredentials = mvar,
59− refreshAction = action
60− }
61−
62−getSTSCredential :: STSCredentialStore -> Endpoint -> NC.Manager -> IO (CredentialValue, Bool)
63−getSTSCredential store ep mgr = M.modifyMVar (cachedCredentials store) $ \cc@(v, expiry) -> do
64− now <- getCurrentTime
65− if diffUTCTime now (coerce expiry) > 0
66− then do
67− res <- refreshAction store ep mgr
68− return (res, (fst res, True))
69− else return (cc, (v, False))
70−
71−data Creds
72− = CredsStatic CredentialValue
73− | CredsSTS STSCredentialStore
74−
75−getCredential :: Creds -> Endpoint -> NC.Manager -> IO CredentialValue
76−getCredential (CredsStatic v) _ _ = return v
77−getCredential (CredsSTS s) ep mgr = fst <$> getSTSCredential s ep mgr
src/Network/Minio/Credentials/AssumeRole.hsdeleted−266
@@ -1,266 +0,0 @@
1−--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
3−--
4−-- Licensed under the Apache License, Version 2.0 (the "License");
5−-- you may not use this file except in compliance with the License.
6−-- You may obtain a copy of the License at
7−--
8−-- http://www.apache.org/licenses/LICENSE-2.0
9−--
10−-- Unless required by applicable law or agreed to in writing, software
11−-- distributed under the License is distributed on an "AS IS" BASIS,
12−-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13−-- See the License for the specific language governing permissions and
14−-- limitations under the License.
15−--
16−
17−module Network.Minio.Credentials.AssumeRole where
18−
19−import qualified Data.ByteArray as BA
20−import qualified Data.ByteString.Lazy as LB
21−import qualified Data.Text as T
22−import qualified Data.Time as Time
23−import Data.Time.Units (Second)
24−import Lib.Prelude (UTCTime, throwIO)
25−import Network.HTTP.Client (RequestBody (RequestBodyBS))
26−import qualified Network.HTTP.Client as NC
27−import Network.HTTP.Types (hContentType, methodPost, renderSimpleQuery)
28−import Network.HTTP.Types.Header (hHost)
29−import Network.Minio.Credentials.Types
30−import Network.Minio.Data.Crypto (hashSHA256)
31−import Network.Minio.Errors (MErrV (..))
32−import Network.Minio.Sign.V4
33−import Network.Minio.Utils (getHostHeader, httpLbs)
34−import Network.Minio.XmlCommon
35−import Text.XML.Cursor hiding (bool)
36−
37−stsVersion :: ByteString
38−stsVersion = "2011-06-15"
39−
40−defaultDurationSeconds :: Second
41−defaultDurationSeconds = 3600
42−
43−-- | Assume Role API argument.
44−--
45−-- @since 1.7.0
46−data STSAssumeRole = STSAssumeRole
47− { -- | Credentials to use in the AssumeRole STS API.
48− sarCredentials :: CredentialValue,
49− -- | Optional settings.
50− sarOptions :: STSAssumeRoleOptions
51− }
52−
53−-- | Options for STS Assume Role API.
54−data STSAssumeRoleOptions = STSAssumeRoleOptions
55− { -- | STS endpoint to which the request will be made. For MinIO, this is the
56− -- same as the server endpoint. For AWS, this has to be the Security Token
57− -- Service endpoint. If using with 'setSTSCredential', this option can be
58− -- left as 'Nothing' and the endpoint in 'ConnectInfo' will be used.
59− saroEndpoint :: Maybe Text,
60− -- | Desired validity for the generated credentials.
61− saroDurationSeconds :: Maybe Second,
62− -- | IAM policy to apply for the generated credentials.
63− saroPolicyJSON :: Maybe ByteString,
64− -- | Location is usually required for AWS.
65− saroLocation :: Maybe Text,
66− saroRoleARN :: Maybe Text,
67− saroRoleSessionName :: Maybe Text
68− }
69−
70−-- | Default STS Assume Role options - all options are Nothing, except for
71−-- duration which is set to 1 hour.
72−defaultSTSAssumeRoleOptions :: STSAssumeRoleOptions
73−defaultSTSAssumeRoleOptions =
74− STSAssumeRoleOptions
75− { saroEndpoint = Nothing,
76− saroDurationSeconds = Just 3600,
77− saroPolicyJSON = Nothing,
78− saroLocation = Nothing,
79− saroRoleARN = Nothing,
80− saroRoleSessionName = Nothing
81− }
82−
83−data AssumeRoleCredentials = AssumeRoleCredentials
84− { arcCredentials :: CredentialValue,
85− arcExpiration :: UTCTime
86− }
87− deriving stock (Show, Eq)
88−
89−data AssumeRoleResult = AssumeRoleResult
90− { arrSourceIdentity :: Text,
91− arrAssumedRoleArn :: Text,
92− arrAssumedRoleId :: Text,
93− arrRoleCredentials :: AssumeRoleCredentials
94− }
95− deriving stock (Show, Eq)
96−
97−-- | parseSTSAssumeRoleResult parses an XML response of the following form:
98−--
99−-- <AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
100−-- <AssumeRoleResult>
101−-- <SourceIdentity>Alice</SourceIdentity>
102−-- <AssumedRoleUser>
103−-- <Arn>arn:aws:sts::123456789012:assumed-role/demo/TestAR</Arn>
104−-- <AssumedRoleId>ARO123EXAMPLE123:TestAR</AssumedRoleId>
105−-- </AssumedRoleUser>
106−-- <Credentials>
107−-- <AccessKeyId>ASIAIOSFODNN7EXAMPLE</AccessKeyId>
108−-- <SecretAccessKey>wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY</SecretAccessKey>
109−-- <SessionToken>
110−-- AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQW
111−-- LWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGd
112−-- QrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU
113−-- 9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz
114−-- +scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==
115−-- </SessionToken>
116−-- <Expiration>2019-11-09T13:34:41Z</Expiration>
117−-- </Credentials>
118−-- <PackedPolicySize>6</PackedPolicySize>
119−-- </AssumeRoleResult>
120−-- <ResponseMetadata>
121−-- <RequestId>c6104cbe-af31-11e0-8154-cbc7ccf896c7</RequestId>
122−-- </ResponseMetadata>
123−-- </AssumeRoleResponse>
124−parseSTSAssumeRoleResult :: (MonadIO m) => ByteString -> Text -> m AssumeRoleResult
125−parseSTSAssumeRoleResult xmldata namespace = do
126− r <- parseRoot $ LB.fromStrict xmldata
127− let s3Elem' = s3Elem namespace
128− sourceIdentity =
129− T.concat $
130− r
131− $/ s3Elem' "AssumeRoleResult"
132− &/ s3Elem' "SourceIdentity"
133− &/ content
134− roleArn =
135− T.concat $
136− r
137− $/ s3Elem' "AssumeRoleResult"
138− &/ s3Elem' "AssumedRoleUser"
139− &/ s3Elem' "Arn"
140− &/ content
141− roleId =
142− T.concat $
143− r
144− $/ s3Elem' "AssumeRoleResult"
145− &/ s3Elem' "AssumedRoleUser"
146− &/ s3Elem' "AssumedRoleId"
147− &/ content
148−
149− convSB :: Text -> BA.ScrubbedBytes
150− convSB = BA.convert . (encodeUtf8 :: Text -> ByteString)
151−
152− credsInfo = do
153− cr <-
154− maybe (Left $ MErrVXmlParse "No Credentials Element found") Right $
155− listToMaybe $
156− r $/ s3Elem' "AssumeRoleResult" &/ s3Elem' "Credentials"
157− let cur = fromNode $ node cr
158− return
159− ( CredentialValue
160− { cvAccessKey =
161− coerce $
162− T.concat $
163− cur $/ s3Elem' "AccessKeyId" &/ content,
164− cvSecretKey =
165− coerce $
166− convSB $
167− T.concat $
168− cur
169− $/ s3Elem' "SecretAccessKey"
170− &/ content,
171− cvSessionToken =
172− Just $
173− coerce $
174− convSB $
175− T.concat $
176− cur
177− $/ s3Elem' "SessionToken"
178− &/ content
179− },
180− T.concat $ cur $/ s3Elem' "Expiration" &/ content
181− )
182− creds <- either throwIO pure credsInfo
183− expiry <- parseS3XMLTime $ snd creds
184− let roleCredentials =
185− AssumeRoleCredentials
186− { arcCredentials = fst creds,
187− arcExpiration = expiry
188− }
189− return
190− AssumeRoleResult
191− { arrSourceIdentity = sourceIdentity,
192− arrAssumedRoleArn = roleArn,
193− arrAssumedRoleId = roleId,
194− arrRoleCredentials = roleCredentials
195− }
196−
197−instance STSCredentialProvider STSAssumeRole where
198− getSTSEndpoint = saroEndpoint . sarOptions
199− retrieveSTSCredentials sar (host', port', isSecure') mgr = do
200− -- Assemble STS request
201− let requiredParams =
202− [ ("Action", "AssumeRole"),
203− ("Version", stsVersion)
204− ]
205− opts = sarOptions sar
206−
207− durSecs :: Int =
208− fromIntegral $
209− fromMaybe defaultDurationSeconds $
210− saroDurationSeconds opts
211− otherParams =
212− [ ("RoleArn",) . encodeUtf8 <$> saroRoleARN opts,
213− ("RoleSessionName",) . encodeUtf8 <$> saroRoleSessionName opts,
214− Just ("DurationSeconds", show durSecs),
215− ("Policy",) <$> saroPolicyJSON opts
216− ]
217− parameters = requiredParams ++ catMaybes otherParams
218− (host, port, isSecure) =
219− case getSTSEndpoint sar of
220− Just ep ->
221− let endPt = NC.parseRequest_ $ toString ep
222− in (NC.host endPt, NC.port endPt, NC.secure endPt)
223− Nothing -> (host', port', isSecure')
224− reqBody = renderSimpleQuery False parameters
225− req =
226− NC.defaultRequest
227− { NC.host = host,
228− NC.port = port,
229− NC.secure = isSecure,
230− NC.method = methodPost,
231− NC.requestHeaders =
232− [ (hHost, getHostHeader (host, port)),
233− (hContentType, "application/x-www-form-urlencoded")
234− ],
235− NC.requestBody = RequestBodyBS reqBody
236− }
237−
238− -- Sign the STS request.
239− timeStamp <- liftIO Time.getCurrentTime
240− let sp =
241− SignParams
242− { spAccessKey = coerce $ cvAccessKey $ sarCredentials sar,
243− spSecretKey = coerce $ cvSecretKey $ sarCredentials sar,
244− spSessionToken = coerce $ cvSessionToken $ sarCredentials sar,
245− spService = ServiceSTS,
246− spTimeStamp = timeStamp,
247− spRegion = saroLocation opts,
248− spExpirySecs = Nothing,
249− spPayloadHash = Just $ hashSHA256 reqBody
250− }
251− signHeaders = signV4 sp req
252− signedReq =
253− req
254− { NC.requestHeaders = NC.requestHeaders req ++ signHeaders
255− }
256−
257− -- Make the STS request
258− resp <- httpLbs signedReq mgr
259− result <-
260− parseSTSAssumeRoleResult
261− (toStrict $ NC.responseBody resp)
262− "https://sts.amazonaws.com/doc/2011-06-15/"
263− return
264− ( arcCredentials $ arrRoleCredentials result,
265− coerce $ arcExpiration $ arrRoleCredentials result
266− )
src/Network/Minio/Credentials/Types.hsdeleted−90
@@ -1,90 +0,0 @@
1−--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
3−--
4−-- Licensed under the Apache License, Version 2.0 (the "License");
5−-- you may not use this file except in compliance with the License.
6−-- You may obtain a copy of the License at
7−--
8−-- http://www.apache.org/licenses/LICENSE-2.0
9−--
10−-- Unless required by applicable law or agreed to in writing, software
11−-- distributed under the License is distributed on an "AS IS" BASIS,
12−-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13−-- See the License for the specific language governing permissions and
14−-- limitations under the License.
15−--
16−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
17−{-# LANGUAGE StrictData #-}
18−
19−module Network.Minio.Credentials.Types where
20−
21−import qualified Data.ByteArray as BA
22−import Lib.Prelude (UTCTime)
23−import qualified Network.HTTP.Client as NC
24−
25−-- | Access Key type.
26−newtype AccessKey = AccessKey {unAccessKey :: Text}
27− deriving stock (Show)
28− deriving newtype (Eq, IsString, Semigroup, Monoid)
29−
30−-- | Secret Key type - has a show instance that does not print the value.
31−newtype SecretKey = SecretKey {unSecretKey :: BA.ScrubbedBytes}
32− deriving stock (Show)
33− deriving newtype (Eq, IsString, Semigroup, Monoid)
34−
35−-- | Session Token type - has a show instance that does not print the value.
36−newtype SessionToken = SessionToken {unSessionToken :: BA.ScrubbedBytes}
37− deriving stock (Show)
38− deriving newtype (Eq, IsString, Semigroup, Monoid)
39−
40−-- | Object storage credential data type. It has support for the optional
41−-- [SessionToken](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html)
42−-- for using temporary credentials requested via STS.
43−--
44−-- The show instance for this type does not print the value of secrets for
45−-- security.
46−--
47−-- @since 1.7.0
48−data CredentialValue = CredentialValue
49− { cvAccessKey :: AccessKey,
50− cvSecretKey :: SecretKey,
51− cvSessionToken :: Maybe SessionToken
52− }
53− deriving stock (Eq, Show)
54−
55−scrubbedToText :: BA.ScrubbedBytes -> Text
56−scrubbedToText =
57− let b2t :: ByteString -> Text
58− b2t = decodeUtf8
59− s2b :: BA.ScrubbedBytes -> ByteString
60− s2b = BA.convert
61− in b2t . s2b
62−
63−-- | Convert a 'CredentialValue' to a text tuple. Use this to output the
64−-- credential to files or other programs.
65−credentialValueText :: CredentialValue -> (Text, Text, Maybe Text)
66−credentialValueText cv =
67− ( coerce $ cvAccessKey cv,
68− (scrubbedToText . coerce) $ cvSecretKey cv,
69− scrubbedToText . coerce <$> cvSessionToken cv
70− )
71−
72−-- | Endpoint represented by host, port and TLS enabled flag.
73−type Endpoint = (ByteString, Int, Bool)
74−
75−-- | Typeclass for STS credential providers.
76−--
77−-- @since 1.7.0
78−class STSCredentialProvider p where
79− retrieveSTSCredentials ::
80− p ->
81− -- | STS Endpoint (host, port, isSecure)
82− Endpoint ->
83− NC.Manager ->
84− IO (CredentialValue, ExpiryTime)
85− getSTSEndpoint :: p -> Maybe Text
86−
87−-- | 'ExpiryTime' represents a time at which a credential expires.
88−newtype ExpiryTime = ExpiryTime {unExpiryTime :: UTCTime}
89− deriving stock (Show)
90− deriving newtype (Eq)
src/Network/Minio/Data.hs+559−697
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017, 2018 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -13,64 +13,45 @@
13-- See the License for the specific language governing permissions and
13-- See the License for the specific language governing permissions and
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16−{-# LANGUAGE CPP #-}
17−{-# LANGUAGE GADTs #-}
18−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
19−{-# LANGUAGE StrictData #-}
20−{-# LANGUAGE TypeFamilies #-}
21
16
17+{-# LANGUAGE CPP #-}
18+{-# LANGUAGE GADTs #-}
19+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
20+{-# LANGUAGE TypeFamilies #-}
22module Network.Minio.Data where
21module Network.Minio.Data where
23
22
24−import qualified Conduit as C
23+import qualified Conduit as C
25−import qualified Control.Concurrent.MVar as M
24+import qualified Control.Concurrent.MVar as M
26−import Control.Monad.Trans.Except (throwE)
25+import Control.Monad.IO.Unlift (UnliftIO (..), askUnliftIO,
26+ withUnliftIO)
27−import Control.Monad.Trans.Resource
27+import Control.Monad.Trans.Resource
28− ( MonadResource,
29− MonadThrow (..),
30− MonadUnliftIO,
31− ResourceT,
32− runResourceT,
33− )
34−import qualified Data.Aeson as A
28+import qualified Data.Aeson as A
35−import qualified Data.ByteArray as BA
29+import qualified Data.ByteArray as BA
36−import qualified Data.ByteString as B
30+import qualified Data.ByteString as B
37−import qualified Data.ByteString.Lazy as LB
31+import Data.CaseInsensitive (mk)
38−import Data.Default.Class (def)
39−import qualified Data.HashMap.Strict as H
32+import qualified Data.HashMap.Strict as H
40−import qualified Data.Ini as Ini
33+import qualified Data.Ini as Ini
41−import qualified Data.List as List
34+import Data.String (IsString (..))
42−import qualified Data.Text as T
35+import qualified Data.Text as T
43−import qualified Data.Text.Encoding as TE
36+import qualified Data.Text.Encoding as TE
44−import Data.Time (defaultTimeLocale, formatTime)
37+import Data.Time (defaultTimeLocale, formatTime)
45−import Lib.Prelude (UTCTime, throwIO)
38+import GHC.Show (Show (show))
46−import qualified Network.Connection as Conn
39+import qualified Network.Connection as Conn
47−import Network.HTTP.Client (defaultManagerSettings)
40+import Network.HTTP.Client (defaultManagerSettings)
48−import qualified Network.HTTP.Client.TLS as TLS
41+import qualified Network.HTTP.Client.TLS as TLS
49−import qualified Network.HTTP.Conduit as NC
42+import qualified Network.HTTP.Conduit as NC
50−import Network.HTTP.Types
43+import Network.HTTP.Types (ByteRange, Header, Method, Query,
51− ( ByteRange,
52− Header,
53− Method,
54− Query,
55− hRange,
44+ hRange)
56− )
57−import qualified Network.HTTP.Types as HT
45+import qualified Network.HTTP.Types as HT
58−import Network.Minio.Credentials
59−import Network.Minio.Data.Crypto
60− ( encodeToBase64,
61− hashMD5ToBase64,
62− )
63−import Network.Minio.Data.Time (UrlExpiry)
64−import Network.Minio.Errors
46+import Network.Minio.Errors
65− ( MErrV (MErrVInvalidEncryptionKeyLength, MErrVMissingCredentials),
66− MinioErr (..),
67− )
68−import Network.Minio.Utils
69−import System.Directory (doesFileExist, getHomeDirectory)
47+import System.Directory (doesFileExist, getHomeDirectory)
70−import qualified System.Environment as Env
48+import qualified System.Environment as Env
71−import System.FilePath.Posix (combine)
49+import System.FilePath.Posix (combine)
50+import Text.XML
72−import qualified UnliftIO as U
51+import qualified UnliftIO as U
73−import qualified UnliftIO.MVar as UM
52+
53+import Lib.Prelude
54+import Network.Minio.Data.Crypto
74
55
75-- | max obj size is 5TiB
56-- | max obj size is 5TiB
76maxObjectSize :: Int64
57maxObjectSize :: Int64
@@ -90,157 +71,125 @@ maxMultipartParts = 10000
90-- TODO: Add a type which provides typed constants for region. this
71-- TODO: Add a type which provides typed constants for region. this
91-- type should have a IsString instance to infer the appropriate
72-- type should have a IsString instance to infer the appropriate
92-- constant.
73-- constant.
93−
94-- | awsRegionMap - library constant
74-- | awsRegionMap - library constant
95awsRegionMap :: H.HashMap Text Text
75awsRegionMap :: H.HashMap Text Text
96−awsRegionMap =
76+awsRegionMap = H.fromList [
97− H.fromList
98− [ ("us-east-1", "s3.us-east-1.amazonaws.com"),
77+ ("us-east-1", "s3.amazonaws.com")
99− ("us-east-2", "s3.us-east-2.amazonaws.com"),
78+ , ("us-east-2", "s3-us-east-2.amazonaws.com")
100− ("us-west-1", "s3.us-west-1.amazonaws.com"),
79+ , ("us-west-1", "s3-us-west-1.amazonaws.com")
101− ("us-west-2", "s3.us-west-2.amazonaws.com"),
80+ , ("us-west-2", "s3-us-west-2.amazonaws.com")
102− ("ca-central-1", "s3.ca-central-1.amazonaws.com"),
81+ , ("ca-central-1", "s3-ca-central-1.amazonaws.com")
103− ("ap-south-1", "s3.ap-south-1.amazonaws.com"),
82+ , ("ap-south-1", "s3-ap-south-1.amazonaws.com")
104− ("ap-south-2", "s3.ap-south-2.amazonaws.com"),
105− ("ap-northeast-1", "s3.ap-northeast-1.amazonaws.com"),
83+ , ("ap-northeast-1", "s3-ap-northeast-1.amazonaws.com")
106− ("ap-northeast-2", "s3.ap-northeast-2.amazonaws.com"),
84+ , ("ap-northeast-2", "s3-ap-northeast-2.amazonaws.com")
107− ("ap-northeast-3", "s3.ap-northeast-3.amazonaws.com"),
108− ("ap-southeast-1", "s3.ap-southeast-1.amazonaws.com"),
85+ , ("ap-southeast-1", "s3-ap-southeast-1.amazonaws.com")
109− ("ap-southeast-2", "s3.ap-southeast-2.amazonaws.com"),
86+ , ("ap-southeast-2", "s3-ap-southeast-2.amazonaws.com")
110− ("ap-southeast-3", "s3.ap-southeast-3.amazonaws.com"),
111− ("eu-west-1", "s3.eu-west-1.amazonaws.com"),
87+ , ("eu-west-1", "s3-eu-west-1.amazonaws.com")
112− ("eu-west-2", "s3.eu-west-2.amazonaws.com"),
88+ , ("eu-west-2", "s3-eu-west-2.amazonaws.com")
113− ("eu-west-3", "s3.eu-west-3.amazonaws.com"),
114− ("eu-central-1", "s3.eu-central-1.amazonaws.com"),
89+ , ("eu-central-1", "s3-eu-central-1.amazonaws.com")
115− ("eu-central-2", "s3.eu-central-2.amazonaws.com"),
116− ("eu-south-1", "s3.eu-south-1.amazonaws.com"),
90+ , ("sa-east-1", "s3-sa-east-1.amazonaws.com")
117− ("eu-south-2", "s3.eu-south-2.amazonaws.com"),
118− ("af-south-1", "s3.af-south-1.amazonaws.com"),
119− ("ap-east-1", "s3.ap-east-1.amazonaws.com"),
120− ("cn-north-1", "s3.cn-north-1.amazonaws.com.cn"),
121− ("cn-northwest-1", "s3.cn-northwest-1.amazonaws.com.cn"),
122− ("eu-north-1", "s3.eu-north-1.amazonaws.com"),
123− ("me-south-1", "s3.me-south-1.amazonaws.com"),
124− ("me-central-1", "s3.me-central-1.amazonaws.com"),
125− ("us-gov-east-1", "s3.us-gov-east-1.amazonaws.com"),
126− ("us-gov-west-1", "s3.us-gov-west-1.amazonaws.com"),
127− ("sa-east-1", "s3.sa-east-1.amazonaws.com")
128− ]
91+ ]
129
92
130-- | Connection Info data type. To create a 'ConnectInfo' value,
93-- | Connection Info data type. To create a 'ConnectInfo' value,
131-- enable the @OverloadedStrings@ language extension and use the
94-- enable the @OverloadedStrings@ language extension and use the
132-- `IsString` instance to provide a URL, for example:
95-- `IsString` instance to provide a URL, for example:
133--
96--
134-- > let c :: ConnectInfo = "https://play.min.io"
97-- > let c :: ConnectInfo = "https://play.min.io"
135−data ConnectInfo = ConnectInfo
98+data ConnectInfo =
136− { connectHost :: Text,
99+ ConnectInfo { connectHost :: Text
137− connectPort :: Int,
100+ , connectPort :: Int
138− connectCreds :: Creds,
101+ , connectAccessKey :: Text
102+ , connectSecretKey :: Text
139− connectIsSecure :: Bool,
103+ , connectIsSecure :: Bool
140− connectRegion :: Region,
104+ , connectRegion :: Region
141− connectAutoDiscoverRegion :: Bool,
105+ , connectAutoDiscoverRegion :: Bool
142− connectDisableTLSCertValidation :: Bool
106+ , connectDisableTLSCertValidation :: Bool
143− }
107+ } deriving (Eq, Show)
144−
145−getEndpoint :: ConnectInfo -> Endpoint
146−getEndpoint ci = (encodeUtf8 $ connectHost ci, connectPort ci, connectIsSecure ci)
147
108
148instance IsString ConnectInfo where
109instance IsString ConnectInfo where
149− fromString str =
110+ fromString str =
150− let req = NC.parseRequest_ str
111+ let req = NC.parseRequest_ str
151− in ConnectInfo
112+ in ConnectInfo
152− { connectHost = TE.decodeUtf8 $ NC.host req,
113+ { connectHost = TE.decodeUtf8 $ NC.host req
153− connectPort = NC.port req,
114+ , connectPort = NC.port req
154− connectCreds = CredsStatic $ CredentialValue mempty mempty mempty,
115+ , connectAccessKey = ""
116+ , connectSecretKey = ""
155− connectIsSecure = NC.secure req,
117+ , connectIsSecure = NC.secure req
156− connectRegion = "",
118+ , connectRegion = ""
157− connectAutoDiscoverRegion = True,
119+ , connectAutoDiscoverRegion = True
158− connectDisableTLSCertValidation = False
120+ , connectDisableTLSCertValidation = False
159− }
121+ }
160−
122+
123+-- | Contains access key and secret key to access object storage.
124+data Credentials = Credentials { cAccessKey :: Text
125+ , cSecretKey :: Text
126+ } deriving (Eq, Show)
127+
161−-- | A 'CredentialLoader' is an action that may return a 'CredentialValue'.
128+-- | A Provider is an action that may return Credentials. Providers
162−-- Loaders may be chained together using 'findFirst'.
129+-- may be chained together using 'findFirst'.
163−--
130+type Provider = IO (Maybe Credentials)
164−-- @since 1.7.0
131+
165−type CredentialLoader = IO (Maybe CredentialValue)
166−
167−-- | Combines the given list of loaders, by calling each one in
132+-- | Combines the given list of providers, by calling each one in
168−-- order until a 'CredentialValue' is returned.
133+-- order until Credentials are found.
169−findFirst :: [CredentialLoader] -> IO (Maybe CredentialValue)
134+findFirst :: [Provider] -> Provider
170findFirst [] = return Nothing
135findFirst [] = return Nothing
171−findFirst (f : fs) = do
136+findFirst (f:fs) = do c <- f
172− c <- f
173− maybe (findFirst fs) (return . Just) c
137+ maybe (findFirst fs) (return . Just) c
174
138
175−-- | This action returns a 'CredentialValue' populated from
176−-- @~\/.aws\/credentials@
139+-- | This Provider loads `Credentials` from @~\/.aws\/credentials@
177−fromAWSConfigFile :: CredentialLoader
140+fromAWSConfigFile :: Provider
178fromAWSConfigFile = do
141fromAWSConfigFile = do
179− credsE <- runExceptT $ do
142+ credsE <- runExceptT $ do
180− homeDir <- lift getHomeDirectory
143+ homeDir <- lift $ getHomeDirectory
181− let awsCredsFile = homeDir `combine` ".aws" `combine` "credentials"
144+ let awsCredsFile = homeDir `combine` ".aws" `combine` "credentials"
182− fileExists <- lift $ doesFileExist awsCredsFile
145+ fileExists <- lift $ doesFileExist awsCredsFile
183− bool (throwE "FileNotFound") (return ()) fileExists
146+ bool (throwE "FileNotFound") (return ()) fileExists
184− ini <- ExceptT $ Ini.readIniFile awsCredsFile
147+ ini <- ExceptT $ Ini.readIniFile awsCredsFile
185− akey <-
186− ExceptT $
148+ akey <- ExceptT $ return
187− return $
188− Ini.lookupValue "default" "aws_access_key_id" ini
149+ $ Ini.lookupValue "default" "aws_access_key_id" ini
189− skey <-
190− ExceptT $
150+ skey <- ExceptT $ return
191− return $
192− Ini.lookupValue "default" "aws_secret_access_key" ini
151+ $ Ini.lookupValue "default" "aws_secret_access_key" ini
193− return $ CredentialValue (coerce akey) (fromString $ T.unpack skey) Nothing
152+ return $ Credentials akey skey
194− return $ either (const Nothing) Just credsE
153+ return $ hush credsE
195−
154+
196−-- | This action returns a 'CredentialValue` populated from @AWS_ACCESS_KEY_ID@
155+-- | This Provider loads `Credentials` from @AWS_ACCESS_KEY_ID@ and
197−-- and @AWS_SECRET_ACCESS_KEY@ environment variables.
156+-- @AWS_SECRET_ACCESS_KEY@ environment variables.
198−fromAWSEnv :: CredentialLoader
157+fromAWSEnv :: Provider
199fromAWSEnv = runMaybeT $ do
158fromAWSEnv = runMaybeT $ do
200− akey <- MaybeT $ Env.lookupEnv "AWS_ACCESS_KEY_ID"
159+ akey <- MaybeT $ Env.lookupEnv "AWS_ACCESS_KEY_ID"
201− skey <- MaybeT $ Env.lookupEnv "AWS_SECRET_ACCESS_KEY"
160+ skey <- MaybeT $ Env.lookupEnv "AWS_SECRET_ACCESS_KEY"
202− return $ CredentialValue (fromString akey) (fromString skey) Nothing
161+ return $ Credentials (T.pack akey) (T.pack skey)
203
162
204−-- | This action returns a 'CredentialValue' populated from @MINIO_ACCESS_KEY@
163+-- | This Provider loads `Credentials` from @MINIO_ACCESS_KEY@ and
205−-- and @MINIO_SECRET_KEY@ environment variables.
164+-- @MINIO_SECRET_KEY@ environment variables.
206−fromMinioEnv :: CredentialLoader
165+fromMinioEnv :: Provider
207fromMinioEnv = runMaybeT $ do
166fromMinioEnv = runMaybeT $ do
208− akey <- MaybeT $ Env.lookupEnv "MINIO_ACCESS_KEY"
167+ akey <- MaybeT $ Env.lookupEnv "MINIO_ACCESS_KEY"
209− skey <- MaybeT $ Env.lookupEnv "MINIO_SECRET_KEY"
168+ skey <- MaybeT $ Env.lookupEnv "MINIO_SECRET_KEY"
210− return $ CredentialValue (fromString akey) (fromString skey) Nothing
169+ return $ Credentials (T.pack akey) (T.pack skey)
211−
170+
212−-- | setCredsFrom retrieves access credentials from the first action in the
171+-- | setCredsFrom retrieves access credentials from the first
213−-- given list that succeeds and sets it in the 'ConnectInfo'.
172+-- `Provider` form the given list that succeeds and sets it in the
173+-- `ConnectInfo`.
214−setCredsFrom :: [CredentialLoader] -> ConnectInfo -> IO ConnectInfo
174+setCredsFrom :: [Provider] -> ConnectInfo -> IO ConnectInfo
215−setCredsFrom ps ci = do
175+setCredsFrom ps ci = do pMay <- findFirst ps
216− pMay <- findFirst ps
217− maybe
176+ maybe
218− (throwIO MErrVMissingCredentials)
177+ (throwIO MErrVMissingCredentials)
219− (return . (`setCreds` ci))
178+ (return . (flip setCreds ci))
220− pMay
179+ pMay
221−
180+
222−-- | setCreds sets the given `CredentialValue` in the `ConnectInfo`.
181+-- | setCreds sets the given `Credentials` in the `ConnectInfo`.
223−setCreds :: CredentialValue -> ConnectInfo -> ConnectInfo
182+setCreds :: Credentials -> ConnectInfo -> ConnectInfo
224−setCreds cv connInfo =
183+setCreds (Credentials accessKey secretKey) connInfo =
225− connInfo
184+ connInfo { connectAccessKey = accessKey
226− { connectCreds = CredsStatic cv
185+ , connectSecretKey = secretKey
227− }
186+ }
228−
229−-- | 'setSTSCredential' configures `ConnectInfo` to retrieve temporary
230−-- credentials via the STS API on demand. It is automatically refreshed on
231−-- expiry.
232−setSTSCredential :: (STSCredentialProvider p) => p -> ConnectInfo -> IO ConnectInfo
233−setSTSCredential p ci = do
234− store <- initSTSCredential p
235− return ci {connectCreds = CredsSTS store}
236
187
237-- | Set the S3 region parameter in the `ConnectInfo`
188-- | Set the S3 region parameter in the `ConnectInfo`
238setRegion :: Region -> ConnectInfo -> ConnectInfo
189setRegion :: Region -> ConnectInfo -> ConnectInfo
239−setRegion r connInfo =
190+setRegion r connInfo = connInfo { connectRegion = r
240− connInfo
241− { connectRegion = r,
242− connectAutoDiscoverRegion = False
191+ , connectAutoDiscoverRegion = False
243− }
192+ }
244
193
245-- | Check if the connection to object storage server is secure
194-- | Check if the connection to object storage server is secure
246-- (i.e. uses TLS)
195-- (i.e. uses TLS)
@@ -252,10 +201,16 @@ isConnectInfoSecure = connectIsSecure
252-- certificates. Note that this option has no effect, if you provide
201-- certificates. Note that this option has no effect, if you provide
253-- your own Manager in `mkMinioConn`.
202-- your own Manager in `mkMinioConn`.
254disableTLSCertValidation :: ConnectInfo -> ConnectInfo
203disableTLSCertValidation :: ConnectInfo -> ConnectInfo
255−disableTLSCertValidation c = c {connectDisableTLSCertValidation = True}
204+disableTLSCertValidation c = c { connectDisableTLSCertValidation = True }
256
205
257getHostAddr :: ConnectInfo -> ByteString
206getHostAddr :: ConnectInfo -> ByteString
258−getHostAddr ci = getHostHeader (encodeUtf8 $ connectHost ci, connectPort ci)
207+getHostAddr ci = if | port == 80 || port == 443 -> toS host
208+ | otherwise -> toS $
209+ T.concat [ host, ":" , Lib.Prelude.show port]
210+ where
211+ port = connectPort ci
212+ host = connectHost ci
213+
259
214
260-- | Default Google Compute Storage ConnectInfo. Works only for
215-- | Default Google Compute Storage ConnectInfo. Works only for
261-- "Simple Migration" use-case with interoperability mode enabled on
216-- "Simple Migration" use-case with interoperability mode enabled on
@@ -264,25 +219,23 @@ getHostAddr ci = getHostHeader (encodeUtf8 $ connectHost ci, connectPort ci)
264--
219--
265-- Credentials should be supplied before use.
220-- Credentials should be supplied before use.
266gcsCI :: ConnectInfo
221gcsCI :: ConnectInfo
267−gcsCI =
268− setRegion
222+gcsCI = setRegion "us"
269− "us"
270− "https://storage.googleapis.com"
223+ "https://storage.googleapis.com"
224+
271
225
272-- | Default AWS S3 ConnectInfo. Connects to "us-east-1". Credentials
226-- | Default AWS S3 ConnectInfo. Connects to "us-east-1". Credentials
273-- should be supplied before use.
227-- should be supplied before use.
274awsCI :: ConnectInfo
228awsCI :: ConnectInfo
275awsCI = "https://s3.amazonaws.com"
229awsCI = "https://s3.amazonaws.com"
276
230
231+
277-- | <https://play.min.io MinIO Play Server>
232-- | <https://play.min.io MinIO Play Server>
278-- ConnectInfo. Credentials are already filled in.
233-- ConnectInfo. Credentials are already filled in.
279minioPlayCI :: ConnectInfo
234minioPlayCI :: ConnectInfo
280−minioPlayCI =
281− let playCreds = CredentialValue "Q3AM3UQ867SPQQA43P2F" "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" Nothing
235+minioPlayCI = let playCreds = Credentials "Q3AM3UQ867SPQQA43P2F" "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
282− in setCreds playCreds $
236+ in setCreds playCreds
283− setRegion
237+ $ setRegion "us-east-1"
284− "us-east-1"
285− "https://play.min.io"
238+ "https://play.min.io"
286
239
287-- |
240-- |
288-- Represents a bucket in the object store
241-- Represents a bucket in the object store
@@ -301,147 +254,135 @@ type ETag = Text
301-- | Data type to represent an object encryption key. Create one using
254-- | Data type to represent an object encryption key. Create one using
302-- the `mkSSECKey` function.
255-- the `mkSSECKey` function.
303newtype SSECKey = SSECKey BA.ScrubbedBytes
256newtype SSECKey = SSECKey BA.ScrubbedBytes
304− deriving stock (Eq, Show)
257+ deriving (Eq, Show)
305
258
306-- | Validates that the given ByteString is 32 bytes long and creates
259-- | Validates that the given ByteString is 32 bytes long and creates
307-- an encryption key.
260-- an encryption key.
308−mkSSECKey :: (MonadThrow m) => ByteString -> m SSECKey
261+mkSSECKey :: MonadThrow m => ByteString -> m SSECKey
309−mkSSECKey keyBytes
262+mkSSECKey keyBytes | B.length keyBytes /= 32 =
310− | B.length keyBytes /= 32 =
311− throwM MErrVInvalidEncryptionKeyLength
263+ throwM MErrVInvalidEncryptionKeyLength
312− | otherwise =
264+ | otherwise =
313− return $ SSECKey $ BA.convert keyBytes
265+ return $ SSECKey $ BA.convert keyBytes
314
266
315-- | Data type to represent Server-Side-Encryption settings
267-- | Data type to represent Server-Side-Encryption settings
316data SSE where
268data SSE where
317− -- | Specifies SSE S3 encryption - server manages encryption keys
269+ -- | Specifies SSE S3 encryption - server manages encryption keys
318− SSE :: SSE
270+ SSE :: SSE
319− -- | Specifies that KMS service should be used. The first argument
271+ -- | Specifies that KMS service should be used. The first argument
320− -- to the constructor is the Key Id to be used by the server (if
272+ -- to the constructor is the Key Id to be used by the server (if
321− -- not specified, the default KMS key id is used). The second
273+ -- not specified, the default KMS key id is used). The second
322− -- argument is the optional KMS context that must have a
274+ -- argument is the optional KMS context that must have a
323− -- `A.ToJSON` instance - please refer to the AWS S3 documentation
275+ -- `A.ToJSON` instance - please refer to the AWS S3 documentation
324− -- for detailed information.
276+ -- for detailed information.
325− SSEKMS :: (A.ToJSON a) => Maybe ByteString -> Maybe a -> SSE
277+ SSEKMS :: A.ToJSON a => Maybe ByteString -> Maybe a -> SSE
326− -- | Specifies server-side encryption with customer provided
278+ -- | Specifies server-side encryption with customer provided
327− -- key. The argument is the encryption key to be used.
279+ -- key. The argument is the encryption key to be used.
328− SSEC :: SSECKey -> SSE
280+ SSEC :: SSECKey -> SSE
329
281
330toPutObjectHeaders :: SSE -> [HT.Header]
282toPutObjectHeaders :: SSE -> [HT.Header]
331toPutObjectHeaders sseArg =
283toPutObjectHeaders sseArg =
332− let sseHeader = "x-amz-server-side-encryption"
284+ let sseHeader = "x-amz-server-side-encryption"
333− sseKmsIdHeader = sseHeader <> "-aws-kms-key-id"
285+ sseKmsIdHeader = sseHeader <> "-aws-kms-key-id"
334− sseKmsContextHeader = sseHeader <> "-context"
286+ sseKmsContextHeader = sseHeader <> "-context"
335− ssecAlgo = sseHeader <> "-customer-algorithm"
287+ ssecAlgo = sseHeader <> "-customer-algorithm"
336− ssecKey = sseHeader <> "-customer-key"
288+ ssecKey = sseHeader <> "-customer-key"
337− ssecKeyMD5 = ssecKey <> "-MD5"
289+ ssecKeyMD5 = ssecKey <> "-MD5"
290+
338− in case sseArg of
291+ in case sseArg of
339− SSE -> [(sseHeader, "AES256")]
292+ SSE -> [(sseHeader, "AES256")]
293+
340− SSEKMS keyIdMay ctxMay ->
294+ SSEKMS keyIdMay ctxMay ->
341− [(sseHeader, "aws:kms")]
295+ [(sseHeader, "aws:kms")] ++
342− ++ maybe [] (\k -> [(sseKmsIdHeader, k)]) keyIdMay
296+ maybe [] (\k -> [(sseKmsIdHeader, k)]) keyIdMay ++
343− ++ maybe [] (\k -> [(sseKmsContextHeader, LB.toStrict $ A.encode k)]) ctxMay
297+ maybe [] (\k -> [(sseKmsContextHeader, toS $ A.encode k)]) ctxMay
298+
344− SSEC (SSECKey sb) ->
299+ SSEC (SSECKey sb) ->
345− [ (ssecAlgo, "AES256"),
300+ [(ssecAlgo, "AES256"),
346− (ssecKey, encodeToBase64 sb),
301+ (ssecKey, encodeToBase64 sb),
347− (ssecKeyMD5, hashMD5ToBase64 sb)
302+ (ssecKeyMD5, hashMD5ToBase64 sb)]
348− ]
349
303
350-- | Data type for options in PutObject call. Start with the empty
304-- | Data type for options in PutObject call. Start with the empty
351-- `defaultPutObjectOptions` and use various the various poo*
305-- `defaultPutObjectOptions` and use various the various poo*
352-- accessors.
306-- accessors.
353−data PutObjectOptions = PutObjectOptions
307+data PutObjectOptions = PutObjectOptions {
354− { -- | Set a standard MIME type describing the format of the object.
308+ -- | Set a standard MIME type describing the format of the object.
355− pooContentType :: Maybe Text,
309+ pooContentType :: Maybe Text
356− -- | Set what content encodings have been applied to the object and thus
310+ -- | Set what content encodings have been applied to the object and thus
357− -- what decoding mechanisms must be applied to obtain the media-type
311+ -- what decoding mechanisms must be applied to obtain the media-type
358− -- referenced by the Content-Type header field.
312+ -- referenced by the Content-Type header field.
359− pooContentEncoding :: Maybe Text,
313+ , pooContentEncoding :: Maybe Text
360− -- | Set presentational information for the object.
314+ -- | Set presentational information for the object.
361− pooContentDisposition :: Maybe Text,
315+ , pooContentDisposition :: Maybe Text
362− -- | Set to specify caching behavior for the object along the
316+ -- | Set to specify caching behavior for the object along the
363− -- request/reply chain.
317+ -- request/reply chain.
364− pooCacheControl :: Maybe Text,
318+ , pooCacheControl :: Maybe Text
365− -- | Set to describe the language(s) intended for the audience.
319+ -- | Set to describe the language(s) intended for the audience.
366− pooContentLanguage :: Maybe Text,
320+ , pooContentLanguage :: Maybe Text
367− -- | Set to @STANDARD@ or @REDUCED_REDUNDANCY@ depending on your
321+ -- | Set to @STANDARD@ or @REDUCED_REDUNDANCY@ depending on your
368− -- performance needs, storage class is @STANDARD@ by default (i.e
322+ -- performance needs, storage class is @STANDARD@ by default (i.e
369− -- when Nothing is passed).
323+ -- when Nothing is passed).
370− pooStorageClass :: Maybe Text,
324+ , pooStorageClass :: Maybe Text
371− -- | Set user defined metadata to store with the object.
325+ -- | Set user defined metadata to store with the object.
372− pooUserMetadata :: [(Text, Text)],
326+ , pooUserMetadata :: [(Text, Text)]
373− -- | Set number of worker threads used to upload an object.
327+ -- | Set number of worker threads used to upload an object.
374− pooNumThreads :: Maybe Word,
328+ , pooNumThreads :: Maybe Word
375− -- | Set object encryption parameters for the request.
329+ -- | Set object encryption parameters for the request.
376− pooSSE :: Maybe SSE
330+ , pooSSE :: Maybe SSE
377 }
331 }
378
332
379-- | Provide default `PutObjectOptions`.
333-- | Provide default `PutObjectOptions`.
380defaultPutObjectOptions :: PutObjectOptions
334defaultPutObjectOptions :: PutObjectOptions
381defaultPutObjectOptions = PutObjectOptions Nothing Nothing Nothing Nothing Nothing Nothing [] Nothing Nothing
335defaultPutObjectOptions = PutObjectOptions Nothing Nothing Nothing Nothing Nothing Nothing [] Nothing Nothing
382
336
337+-- | If the given header name has the @X-Amz-Meta-@ prefix, it is
338+-- stripped and a Just is returned.
339+userMetadataHeaderNameMaybe :: Text -> Maybe Text
340+userMetadataHeaderNameMaybe k =
341+ let prefix = T.toCaseFold "X-Amz-Meta-"
342+ n = T.length prefix
343+ in if T.toCaseFold (T.take n k) == prefix
344+ then Just (T.drop n k)
345+ else Nothing
346+
347+addXAmzMetaPrefix :: Text -> Text
348+addXAmzMetaPrefix s | isJust (userMetadataHeaderNameMaybe s) = s
349+ | otherwise = "X-Amz-Meta-" <> s
350+
351+mkHeaderFromMetadata :: [(Text, Text)] -> [HT.Header]
352+mkHeaderFromMetadata = map (\(x, y) -> (mk $ encodeUtf8 $ addXAmzMetaPrefix $ x, encodeUtf8 y))
353+
383pooToHeaders :: PutObjectOptions -> [HT.Header]
354pooToHeaders :: PutObjectOptions -> [HT.Header]
384−pooToHeaders poo =
355+pooToHeaders poo = userMetadata
385− userMetadata
386− ++ mapMaybe tupToMaybe (zip names values)
356+ ++ (catMaybes $ map tupToMaybe (zipWith (,) names values))
387− ++ maybe [] toPutObjectHeaders (pooSSE poo)
357+ ++ maybe [] toPutObjectHeaders (pooSSE poo)
388 where
358 where
389− tupToMaybe (k, Just v) = Just (k, v)
359+ tupToMaybe (k, Just v) = Just (k, v)
390 tupToMaybe (_, Nothing) = Nothing
360 tupToMaybe (_, Nothing) = Nothing
361+
391 userMetadata = mkHeaderFromMetadata $ pooUserMetadata poo
362 userMetadata = mkHeaderFromMetadata $ pooUserMetadata poo
392− names =
363+
393− [ "content-type",
364+ names = ["content-type",
394− "content-encoding",
365+ "content-encoding",
395− "content-disposition",
366+ "content-disposition",
396− "content-language",
367+ "content-language",
397− "cache-control",
368+ "cache-control",
398− "x-amz-storage-class"
369+ "x-amz-storage-class"]
399− ]
400− values =
401− map
402− (fmap encodeUtf8 . (poo &))
370+ values = map (fmap encodeUtf8 . (poo &))
403− [ pooContentType,
404− pooContentEncoding,
371+ [pooContentType, pooContentEncoding,
405− pooContentDisposition,
372+ pooContentDisposition, pooContentLanguage,
406− pooContentLanguage,
407− pooCacheControl,
373+ pooCacheControl, pooStorageClass]
408− pooStorageClass
374+
409− ]
410
375
411-- |
376-- |
412-- BucketInfo returned for list buckets call
377-- BucketInfo returned for list buckets call
413−data BucketInfo = BucketInfo
378+data BucketInfo = BucketInfo {
414− { biName :: Bucket,
379+ biName :: Bucket
415− biCreationDate :: UTCTime
380+ , biCreationDate :: UTCTime
416− }
417− deriving stock (Show, Eq)
381+ } deriving (Show, Eq)
418
382
419-- | A type alias to represent a part-number for multipart upload
383-- | A type alias to represent a part-number for multipart upload
420type PartNumber = Int16
384type PartNumber = Int16
421
385
422−-- | Select part sizes - the logic is that the minimum part-size will
423−-- be 64MiB.
424−selectPartSizes :: Int64 -> [(PartNumber, Int64, Int64)]
425−selectPartSizes size =
426− uncurry (List.zip3 [1 ..]) $
427− List.unzip $
428− loop 0 size
429− where
430− ceil :: Double -> Int64
431− ceil = ceiling
432− partSize =
433− max
434− minPartSize
435− ( ceil $
436− fromIntegral size
437− / fromIntegral maxMultipartParts
438− )
439− m = partSize
440− loop st sz
441− | st > sz = []
442− | st + m >= sz = [(st, sz - st)]
443− | otherwise = (st, m) : loop (st + m) sz
444−
445-- | A type alias to represent an upload-id for multipart upload
386-- | A type alias to represent an upload-id for multipart upload
446type UploadId = Text
387type UploadId = Text
447
388
@@ -450,116 +391,95 @@ type PartTuple = (PartNumber, ETag)
450
391
451-- | Represents result from a listing of object parts of an ongoing
392-- | Represents result from a listing of object parts of an ongoing
452-- multipart upload.
393-- multipart upload.
453−data ListPartsResult = ListPartsResult
394+data ListPartsResult = ListPartsResult {
454− { lprHasMore :: Bool,
395+ lprHasMore :: Bool
455− lprNextPart :: Maybe Int,
396+ , lprNextPart :: Maybe Int
456− lprParts :: [ObjectPartInfo]
397+ , lprParts :: [ObjectPartInfo]
457− }
458− deriving stock (Show, Eq)
398+ } deriving (Show, Eq)
459
399
460-- | Represents information about an object part in an ongoing
400-- | Represents information about an object part in an ongoing
461-- multipart upload.
401-- multipart upload.
462−data ObjectPartInfo = ObjectPartInfo
402+data ObjectPartInfo = ObjectPartInfo {
463− { opiNumber :: PartNumber,
403+ opiNumber :: PartNumber
464− opiETag :: ETag,
404+ , opiETag :: ETag
465− opiSize :: Int64,
405+ , opiSize :: Int64
466− opiModTime :: UTCTime
406+ , opiModTime :: UTCTime
467− }
468− deriving stock (Show, Eq)
407+ } deriving (Show, Eq)
469
408
470-- | Represents result from a listing of incomplete uploads to a
409-- | Represents result from a listing of incomplete uploads to a
471-- bucket.
410-- bucket.
472−data ListUploadsResult = ListUploadsResult
411+data ListUploadsResult = ListUploadsResult {
473− { lurHasMore :: Bool,
412+ lurHasMore :: Bool
474− lurNextKey :: Maybe Text,
413+ , lurNextKey :: Maybe Text
475− lurNextUpload :: Maybe Text,
414+ , lurNextUpload :: Maybe Text
476− lurUploads :: [(Object, UploadId, UTCTime)],
415+ , lurUploads :: [(Object, UploadId, UTCTime)]
477− lurCPrefixes :: [Text]
416+ , lurCPrefixes :: [Text]
478− }
479− deriving stock (Show, Eq)
417+ } deriving (Show, Eq)
480
418
481-- | Represents information about a multipart upload.
419-- | Represents information about a multipart upload.
482−data UploadInfo = UploadInfo
420+data UploadInfo = UploadInfo {
483− { uiKey :: Object,
421+ uiKey :: Object
484− uiUploadId :: UploadId,
422+ , uiUploadId :: UploadId
485− uiInitTime :: UTCTime,
423+ , uiInitTime :: UTCTime
486− uiSize :: Int64
424+ , uiSize :: Int64
487− }
488− deriving stock (Show, Eq)
425+ } deriving (Show, Eq)
489
426
490-- | Represents result from a listing of objects in a bucket.
427-- | Represents result from a listing of objects in a bucket.
491−data ListObjectsResult = ListObjectsResult
428+data ListObjectsResult = ListObjectsResult {
492− { lorHasMore :: Bool,
429+ lorHasMore :: Bool
493− lorNextToken :: Maybe Text,
430+ , lorNextToken :: Maybe Text
494− lorObjects :: [ObjectInfo],
431+ , lorObjects :: [ObjectInfo]
495− lorCPrefixes :: [Text]
432+ , lorCPrefixes :: [Text]
496− }
497− deriving stock (Show, Eq)
433+ } deriving (Show, Eq)
498
434
499-- | Represents result from a listing of objects version 1 in a bucket.
435-- | Represents result from a listing of objects version 1 in a bucket.
500−data ListObjectsV1Result = ListObjectsV1Result
436+data ListObjectsV1Result = ListObjectsV1Result {
501− { lorHasMore' :: Bool,
437+ lorHasMore' :: Bool
502− lorNextMarker :: Maybe Text,
438+ , lorNextMarker :: Maybe Text
503− lorObjects' :: [ObjectInfo],
439+ , lorObjects' :: [ObjectInfo]
504− lorCPrefixes' :: [Text]
440+ , lorCPrefixes' :: [Text]
505− }
506− deriving stock (Show, Eq)
441+ } deriving (Show, Eq)
507
442
508-- | Represents information about an object.
443-- | Represents information about an object.
509data ObjectInfo = ObjectInfo
444data ObjectInfo = ObjectInfo
510− { -- | Object key
511− oiObject :: Object,
445+ { oiObject :: Object -- ^ Object key
512− -- | Modification time of the object
446+ , oiModTime :: UTCTime -- ^ Modification time of the object
513− oiModTime :: UTCTime,
514− -- | ETag of the object
515− oiETag :: ETag,
447+ , oiETag :: ETag -- ^ ETag of the object
516− -- | Size of the object in bytes
448+ , oiSize :: Int64 -- ^ Size of the object in bytes
517− oiSize :: Int64,
518− -- | A map of user-metadata
449+ , oiUserMetadata :: H.HashMap Text Text -- ^ A map of user-metadata
519− -- pairs stored with an
450+ -- pairs stored with an
520− -- object (keys will not
451+ -- object (keys will not
521− -- have the @X-Amz-Meta-@
452+ -- have the @X-Amz-Meta-@
522− -- prefix).
453+ -- prefix).
523− oiUserMetadata :: H.HashMap Text Text,
524− -- | A map of metadata
454+ , oiMetadata :: H.HashMap Text Text -- ^ A map of metadata
525− -- key-value pairs (not
455+ -- key-value pairs (not
526− -- including the
456+ -- including the
527− -- user-metadata pairs)
457+ -- user-metadata pairs)
528− oiMetadata :: H.HashMap Text Text
529− }
530− deriving stock (Show, Eq)
458+ } deriving (Show, Eq)
531
459
532-- | Represents source object in server-side copy object
460-- | Represents source object in server-side copy object
533data SourceInfo = SourceInfo
461data SourceInfo = SourceInfo
534− { -- | Bucket containing the source object
462+ { srcBucket :: Text -- ^ Bucket containing the source object
535− srcBucket :: Text,
536− -- | Source object key
537− srcObject :: Text,
463+ , srcObject :: Text -- ^ Source object key
538− -- | Source object
464+ , srcRange :: Maybe (Int64, Int64) -- ^ Source object
539− -- byte-range
465+ -- byte-range
540− -- (inclusive)
466+ -- (inclusive)
541− srcRange :: Maybe (Int64, Int64),
542− -- | ETag condition on source -
467+ , srcIfMatch :: Maybe Text -- ^ ETag condition on source -
543− -- object is copied only if the
468+ -- object is copied only if the
544− -- source object's ETag matches
469+ -- source object's ETag matches
545− -- this value.
470+ -- this value.
546− srcIfMatch :: Maybe Text,
547− -- | ETag not match condition
471+ , srcIfNoneMatch :: Maybe Text -- ^ ETag not match condition
548− -- on source - object is copied
472+ -- on source - object is copied
549− -- if ETag does not match this
473+ -- if ETag does not match this
550− -- value.
474+ -- value.
551− srcIfNoneMatch :: Maybe Text,
552− -- | Copy source object only
553− -- if the source has been
554− -- modified since this time.
555− srcIfModifiedSince :: Maybe UTCTime,
475+ , srcIfModifiedSince :: Maybe UTCTime -- ^ Copy source object only
476+ -- if the source has been
477+ -- modified since this time.
556− -- | Copy source object only
478+ , srcIfUnmodifiedSince :: Maybe UTCTime -- ^ Copy source object only
557− -- if the source has been
479+ -- if the source has been
558− -- un-modified since this
480+ -- un-modified since this
559− -- given time.
481+ -- given time.
560− srcIfUnmodifiedSince :: Maybe UTCTime
561− }
562− deriving stock (Show, Eq)
482+ } deriving (Show, Eq)
563
483
564-- | Provide a default for `SourceInfo`
484-- | Provide a default for `SourceInfo`
565defaultSourceInfo :: SourceInfo
485defaultSourceInfo :: SourceInfo
@@ -567,12 +487,9 @@ defaultSourceInfo = SourceInfo "" "" Nothing Nothing Nothing Nothing Nothing
567
487
568-- | Represents destination object in server-side copy object
488-- | Represents destination object in server-side copy object
569data DestinationInfo = DestinationInfo
489data DestinationInfo = DestinationInfo
570− { -- | Destination bucket
490+ { dstBucket :: Text -- ^ Destination bucket
571− dstBucket :: Text,
572− -- | Destination object key
491+ , dstObject :: Text -- ^ Destination object key
573− dstObject :: Text
574− }
575− deriving stock (Show, Eq)
492+ } deriving (Show, Eq)
576
493
577-- | Provide a default for `DestinationInfo`
494-- | Provide a default for `DestinationInfo`
578defaultDestinationInfo :: DestinationInfo
495defaultDestinationInfo :: DestinationInfo
@@ -581,106 +498,96 @@ defaultDestinationInfo = DestinationInfo "" ""
581-- | Data type for options when getting an object from the
498-- | Data type for options when getting an object from the
582-- service. Start with the empty `defaultGetObjectOptions` and modify
499-- service. Start with the empty `defaultGetObjectOptions` and modify
583-- it using the goo* functions.
500-- it using the goo* functions.
584−data GetObjectOptions = GetObjectOptions
501+data GetObjectOptions = GetObjectOptions {
585− { -- | Set object's data of given offset begin and end,
502+ -- | Set object's data of given offset begin and end,
586 -- [ByteRangeFromTo 0 9] means first ten bytes of the source object.
503 -- [ByteRangeFromTo 0 9] means first ten bytes of the source object.
587− gooRange :: Maybe ByteRange,
504+ gooRange :: Maybe ByteRange
588 -- | Set matching ETag condition, GetObject which matches the following
505 -- | Set matching ETag condition, GetObject which matches the following
589 -- ETag.
506 -- ETag.
590− gooIfMatch :: Maybe ETag,
507+ , gooIfMatch :: Maybe ETag
591 -- | Set matching ETag none condition, GetObject which does not match
508 -- | Set matching ETag none condition, GetObject which does not match
592 -- the following ETag.
509 -- the following ETag.
593− gooIfNoneMatch :: Maybe ETag,
510+ , gooIfNoneMatch :: Maybe ETag
594 -- | Set object unmodified condition, GetObject unmodified since given time.
511 -- | Set object unmodified condition, GetObject unmodified since given time.
595− gooIfUnmodifiedSince :: Maybe UTCTime,
512+ , gooIfUnmodifiedSince :: Maybe UTCTime
596 -- | Set object modified condition, GetObject modified since given time.
513 -- | Set object modified condition, GetObject modified since given time.
597− gooIfModifiedSince :: Maybe UTCTime,
514+ , gooIfModifiedSince :: Maybe UTCTime
598 -- | Specify SSE-C key
515 -- | Specify SSE-C key
599− gooSSECKey :: Maybe SSECKey
516+ , gooSSECKey :: Maybe SSECKey
600 }
517 }
601
518
602-- | Provide default `GetObjectOptions`.
519-- | Provide default `GetObjectOptions`.
603defaultGetObjectOptions :: GetObjectOptions
520defaultGetObjectOptions :: GetObjectOptions
604defaultGetObjectOptions =
521defaultGetObjectOptions =
605− GetObjectOptions Nothing Nothing Nothing Nothing Nothing Nothing
522+ GetObjectOptions Nothing Nothing Nothing Nothing Nothing Nothing
606
523
607gooToHeaders :: GetObjectOptions -> [HT.Header]
524gooToHeaders :: GetObjectOptions -> [HT.Header]
608−gooToHeaders goo =
525+gooToHeaders goo = rangeHdr ++ zip names values
609− rangeHdr
610− ++ zip names values
611− ++ maybe [] (toPutObjectHeaders . SSEC) (gooSSECKey goo)
526+ ++ maybe [] (toPutObjectHeaders . SSEC) (gooSSECKey goo)
612 where
527 where
613− names =
614− [ "If-Match",
528+ names = ["If-Match",
615− "If-None-Match",
529+ "If-None-Match",
616− "If-Unmodified-Since",
530+ "If-Unmodified-Since",
617− "If-Modified-Since"
531+ "If-Modified-Since"]
618− ]
619− values =
620− mapMaybe
621− (fmap encodeUtf8 . (goo &))
532+ values = mapMaybe (fmap encodeUtf8 . (goo &))
622− [ gooIfMatch,
623− gooIfNoneMatch,
533+ [gooIfMatch, gooIfNoneMatch,
624− fmap formatRFC1123 . gooIfUnmodifiedSince,
534+ fmap formatRFC1123 . gooIfUnmodifiedSince,
625− fmap formatRFC1123 . gooIfModifiedSince
535+ fmap formatRFC1123 . gooIfModifiedSince]
626− ]
627− rangeHdr =
628− maybe [] (\a -> [(hRange, HT.renderByteRanges [a])]) $
536+ rangeHdr = maybe [] (\a -> [(hRange, HT.renderByteRanges [a])])
629− gooRange goo
537+ $ gooRange goo
630
538
631-- | Data type returned by 'getObject' representing the object being
539-- | Data type returned by 'getObject' representing the object being
632-- retrieved. Use the @gor*@ functions to access its contents.
540-- retrieved. Use the @gor*@ functions to access its contents.
633−data GetObjectResponse = GetObjectResponse
541+data GetObjectResponse = GetObjectResponse {
634− { -- | ObjectInfo of the object being retrieved.
542+ -- | ObjectInfo of the object being retrieved.
635− gorObjectInfo :: ObjectInfo,
543+ gorObjectInfo :: ObjectInfo
636− -- | A conduit of the bytes of the object.
544+ -- | A conduit of the bytes of the object.
637− gorObjectStream :: C.ConduitM () ByteString Minio ()
545+ , gorObjectStream :: C.ConduitM () ByteString Minio ()
638− }
546+ }
639
547
640-- | A data-type for events that can occur in the object storage
548-- | A data-type for events that can occur in the object storage
641-- server. Reference:
549-- server. Reference:
642-- https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#supported-notification-event-types
550-- https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#supported-notification-event-types
643−data Event
644− = ObjectCreated
551+data Event = ObjectCreated
645− | ObjectCreatedPut
552+ | ObjectCreatedPut
646− | ObjectCreatedPost
553+ | ObjectCreatedPost
647− | ObjectCreatedCopy
554+ | ObjectCreatedCopy
648− | ObjectCreatedMultipartUpload
555+ | ObjectCreatedMultipartUpload
649− | ObjectRemoved
556+ | ObjectRemoved
650− | ObjectRemovedDelete
557+ | ObjectRemovedDelete
651− | ObjectRemovedDeleteMarkerCreated
558+ | ObjectRemovedDeleteMarkerCreated
652− | ReducedRedundancyLostObject
559+ | ReducedRedundancyLostObject
653− deriving stock (Eq, Show)
560+ deriving (Eq)
654−
561+
655−instance ToText Event where
562+instance Show Event where
656− toText ObjectCreated = "s3:ObjectCreated:*"
563+ show ObjectCreated = "s3:ObjectCreated:*"
657− toText ObjectCreatedPut = "s3:ObjectCreated:Put"
564+ show ObjectCreatedPut = "s3:ObjectCreated:Put"
658− toText ObjectCreatedPost = "s3:ObjectCreated:Post"
565+ show ObjectCreatedPost = "s3:ObjectCreated:Post"
659− toText ObjectCreatedCopy = "s3:ObjectCreated:Copy"
566+ show ObjectCreatedCopy = "s3:ObjectCreated:Copy"
660− toText ObjectCreatedMultipartUpload = "s3:ObjectCreated:MultipartUpload"
567+ show ObjectCreatedMultipartUpload = "s3:ObjectCreated:MultipartUpload"
661− toText ObjectRemoved = "s3:ObjectRemoved:*"
568+ show ObjectRemoved = "s3:ObjectRemoved:*"
662− toText ObjectRemovedDelete = "s3:ObjectRemoved:Delete"
569+ show ObjectRemovedDelete = "s3:ObjectRemoved:Delete"
663− toText ObjectRemovedDeleteMarkerCreated = "s3:ObjectRemoved:DeleteMarkerCreated"
570+ show ObjectRemovedDeleteMarkerCreated = "s3:ObjectRemoved:DeleteMarkerCreated"
664− toText ReducedRedundancyLostObject = "s3:ReducedRedundancyLostObject"
571+ show ReducedRedundancyLostObject = "s3:ReducedRedundancyLostObject"
665
572
666textToEvent :: Text -> Maybe Event
573textToEvent :: Text -> Maybe Event
667textToEvent t = case t of
574textToEvent t = case t of
668− "s3:ObjectCreated:*" -> Just ObjectCreated
575+ "s3:ObjectCreated:*" -> Just ObjectCreated
669− "s3:ObjectCreated:Put" -> Just ObjectCreatedPut
576+ "s3:ObjectCreated:Put" -> Just ObjectCreatedPut
670− "s3:ObjectCreated:Post" -> Just ObjectCreatedPost
577+ "s3:ObjectCreated:Post" -> Just ObjectCreatedPost
671− "s3:ObjectCreated:Copy" -> Just ObjectCreatedCopy
578+ "s3:ObjectCreated:Copy" -> Just ObjectCreatedCopy
672− "s3:ObjectCreated:MultipartUpload" -> Just ObjectCreatedMultipartUpload
579+ "s3:ObjectCreated:MultipartUpload" -> Just ObjectCreatedMultipartUpload
673− "s3:ObjectRemoved:*" -> Just ObjectRemoved
580+ "s3:ObjectRemoved:*" -> Just ObjectRemoved
674− "s3:ObjectRemoved:Delete" -> Just ObjectRemovedDelete
581+ "s3:ObjectRemoved:Delete" -> Just ObjectRemovedDelete
675 "s3:ObjectRemoved:DeleteMarkerCreated" -> Just ObjectRemovedDeleteMarkerCreated
582 "s3:ObjectRemoved:DeleteMarkerCreated" -> Just ObjectRemovedDeleteMarkerCreated
676− "s3:ReducedRedundancyLostObject" -> Just ReducedRedundancyLostObject
583+ "s3:ReducedRedundancyLostObject" -> Just ReducedRedundancyLostObject
677− _ -> Nothing
584+ _ -> Nothing
585+
678
586
679-- | Filter data type - part of notification configuration
587-- | Filter data type - part of notification configuration
680−newtype Filter = Filter
588+data Filter = Filter
681 { fFilter :: FilterKey
589 { fFilter :: FilterKey
682− }
683− deriving stock (Show, Eq)
590+ } deriving (Show, Eq)
684
591
685-- | defaultFilter is empty, used to create a notification
592-- | defaultFilter is empty, used to create a notification
686-- configuration.
593-- configuration.
@@ -688,10 +595,9 @@ defaultFilter :: Filter
688defaultFilter = Filter defaultFilterKey
595defaultFilter = Filter defaultFilterKey
689
596
690-- | FilterKey contains FilterRules, and is part of a Filter.
597-- | FilterKey contains FilterRules, and is part of a Filter.
691−newtype FilterKey = FilterKey
598+data FilterKey = FilterKey
692 { fkKey :: FilterRules
599 { fkKey :: FilterRules
693− }
694− deriving stock (Show, Eq)
600+ } deriving (Show, Eq)
695
601
696-- | defaultFilterKey is empty, used to create notification
602-- | defaultFilterKey is empty, used to create notification
697-- configuration.
603-- configuration.
@@ -699,16 +605,16 @@ defaultFilterKey :: FilterKey
699defaultFilterKey = FilterKey defaultFilterRules
605defaultFilterKey = FilterKey defaultFilterRules
700
606
701-- | FilterRules represents a collection of `FilterRule`s.
607-- | FilterRules represents a collection of `FilterRule`s.
702−newtype FilterRules = FilterRules
608+data FilterRules = FilterRules
703 { frFilterRules :: [FilterRule]
609 { frFilterRules :: [FilterRule]
704− }
705− deriving stock (Show, Eq)
610+ } deriving (Show, Eq)
706
611
707-- | defaultFilterRules is empty, used to create notification
612-- | defaultFilterRules is empty, used to create notification
708-- configuration.
613-- configuration.
709defaultFilterRules :: FilterRules
614defaultFilterRules :: FilterRules
710defaultFilterRules = FilterRules []
615defaultFilterRules = FilterRules []
711
616
617+
712-- | A filter rule that can act based on the suffix or prefix of an
618-- | A filter rule that can act based on the suffix or prefix of an
713-- object. As an example, let's create two filter rules:
619-- object. As an example, let's create two filter rules:
714--
620--
@@ -719,10 +625,9 @@ defaultFilterRules = FilterRules []
719-- for objects having a suffix of ".jpg", and the @prefixRule@
625-- for objects having a suffix of ".jpg", and the @prefixRule@
720-- restricts it to objects having a prefix of "images/".
626-- restricts it to objects having a prefix of "images/".
721data FilterRule = FilterRule
627data FilterRule = FilterRule
722− { frName :: Text,
628+ { frName :: Text
723− frValue :: Text
629+ , frValue :: Text
724− }
725− deriving stock (Show, Eq)
630+ } deriving (Show, Eq)
726
631
727-- | Arn is an alias of Text
632-- | Arn is an alias of Text
728type Arn = Text
633type Arn = Text
@@ -731,12 +636,11 @@ type Arn = Text
731-- notification system. It could represent a Queue, Topic or Lambda
636-- notification system. It could represent a Queue, Topic or Lambda
732-- Function configuration.
637-- Function configuration.
733data NotificationConfig = NotificationConfig
638data NotificationConfig = NotificationConfig
734− { ncId :: Text,
639+ { ncId :: Text
735− ncArn :: Arn,
640+ , ncArn :: Arn
736− ncEvents :: [Event],
641+ , ncEvents :: [Event]
737− ncFilter :: Filter
642+ , ncFilter :: Filter
738− }
739− deriving stock (Show, Eq)
643+ } deriving (Show, Eq)
740
644
741-- | A data-type to represent bucket notification configuration. It is
645-- | A data-type to represent bucket notification configuration. It is
742-- a collection of queue, topic or lambda function configurations. The
646-- a collection of queue, topic or lambda function configurations. The
@@ -744,16 +648,16 @@ data NotificationConfig = NotificationConfig
744-- described at
648-- described at
745-- <https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTnotification.html>
649-- <https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTnotification.html>
746data Notification = Notification
650data Notification = Notification
747− { nQueueConfigurations :: [NotificationConfig],
651+ { nQueueConfigurations :: [NotificationConfig]
748− nTopicConfigurations :: [NotificationConfig],
652+ , nTopicConfigurations :: [NotificationConfig]
749− nCloudFunctionConfigurations :: [NotificationConfig]
653+ , nCloudFunctionConfigurations :: [NotificationConfig]
750− }
751− deriving stock (Show, Eq)
654+ } deriving (Eq, Show)
752
655
753-- | The default notification configuration is empty.
656-- | The default notification configuration is empty.
754defaultNotification :: Notification
657defaultNotification :: Notification
755defaultNotification = Notification [] [] []
658defaultNotification = Notification [] [] []
756
659
660+
757--------------------------------------------------------------------------
661--------------------------------------------------------------------------
758-- Select API Related Types
662-- Select API Related Types
759--------------------------------------------------------------------------
663--------------------------------------------------------------------------
@@ -761,42 +665,38 @@ defaultNotification = Notification [] [] []
761-- | SelectRequest represents the Select API call. Use the
665-- | SelectRequest represents the Select API call. Use the
762-- `selectRequest` function to create a value of this type.
666-- `selectRequest` function to create a value of this type.
763data SelectRequest = SelectRequest
667data SelectRequest = SelectRequest
764− { srExpression :: Text,
668+ { srExpression :: Text
765− srExpressionType :: ExpressionType,
669+ , srExpressionType :: ExpressionType
766− srInputSerialization :: InputSerialization,
670+ , srInputSerialization :: InputSerialization
767− srOutputSerialization :: OutputSerialization,
671+ , srOutputSerialization :: OutputSerialization
768− srRequestProgressEnabled :: Maybe Bool
672+ , srRequestProgressEnabled :: Maybe Bool
769− }
770− deriving stock (Show, Eq)
673+ } deriving (Eq, Show)
771
674
772data ExpressionType = SQL
675data ExpressionType = SQL
773− deriving stock (Show, Eq)
676+ deriving (Eq, Show)
774
677
775-- | InputSerialization represents format information of the input
678-- | InputSerialization represents format information of the input
776-- object being queried. Use one of the smart constructors such as
679-- object being queried. Use one of the smart constructors such as
777-- `defaultCsvInput` as a starting value, and add compression info
680-- `defaultCsvInput` as a starting value, and add compression info
778-- using `setInputCompressionType`
681-- using `setInputCompressionType`
779data InputSerialization = InputSerialization
682data InputSerialization = InputSerialization
780− { isCompressionType :: Maybe CompressionType,
683+ { isCompressionType :: Maybe CompressionType
781− isFormatInfo :: InputFormatInfo
684+ , isFormatInfo :: InputFormatInfo
782− }
783− deriving stock (Show, Eq)
685+ } deriving (Eq, Show)
784
686
785-- | Data type representing the compression setting in a Select
687-- | Data type representing the compression setting in a Select
786-- request.
688-- request.
787−data CompressionType
689+data CompressionType = CompressionTypeNone
788− = CompressionTypeNone
789− | CompressionTypeGzip
690+ | CompressionTypeGzip
790− | CompressionTypeBzip2
691+ | CompressionTypeBzip2
791− deriving stock (Show, Eq)
692+ deriving (Eq, Show)
792
693
793-- | Data type representing input object format information in a
694-- | Data type representing input object format information in a
794-- Select request.
695-- Select request.
795−data InputFormatInfo
796− = InputFormatCSV CSVInputProp
696+data InputFormatInfo = InputFormatCSV CSVInputProp
797− | InputFormatJSON JSONInputProp
697+ | InputFormatJSON JSONInputProp
798− | InputFormatParquet
698+ | InputFormatParquet
799− deriving stock (Show, Eq)
699+ deriving (Eq, Show)
800
700
801-- | defaultCsvInput returns InputSerialization with default CSV
701-- | defaultCsvInput returns InputSerialization with default CSV
802-- format, and without any compression setting.
702-- format, and without any compression setting.
@@ -806,18 +706,14 @@ defaultCsvInput = InputSerialization Nothing (InputFormatCSV defaultCSVProp)
806-- | linesJsonInput returns InputSerialization with JSON line based
706-- | linesJsonInput returns InputSerialization with JSON line based
807-- format with no compression setting.
707-- format with no compression setting.
808linesJsonInput :: InputSerialization
708linesJsonInput :: InputSerialization
809−linesJsonInput =
810− InputSerialization
709+linesJsonInput = InputSerialization Nothing
811− Nothing
812− (InputFormatJSON $ JSONInputProp JSONTypeLines)
710+ (InputFormatJSON $ JSONInputProp JSONTypeLines)
813
711
814-- | documentJsonInput returns InputSerialization with JSON document
712-- | documentJsonInput returns InputSerialization with JSON document
815-- based format with no compression setting.
713-- based format with no compression setting.
816documentJsonInput :: InputSerialization
714documentJsonInput :: InputSerialization
817−documentJsonInput =
818− InputSerialization
715+documentJsonInput = InputSerialization Nothing
819− Nothing
820− (InputFormatJSON $ JSONInputProp JSONTypeDocument)
716+ (InputFormatJSON $ JSONInputProp JSONTypeDocument)
821
717
822-- | defaultParquetInput returns InputSerialization with Parquet
718-- | defaultParquetInput returns InputSerialization with Parquet
823-- format, and no compression setting.
719-- format, and no compression setting.
@@ -826,14 +722,12 @@ defaultParquetInput = InputSerialization Nothing InputFormatParquet
826
722
827-- | setInputCompressionType sets the compression type for the input
723-- | setInputCompressionType sets the compression type for the input
828-- of the SelectRequest
724-- of the SelectRequest
829−setInputCompressionType ::
725+setInputCompressionType :: CompressionType -> SelectRequest
830− CompressionType ->
831− SelectRequest ->
832− SelectRequest
726+ -> SelectRequest
833setInputCompressionType c i =
727setInputCompressionType c i =
834− let is = srInputSerialization i
728+ let is = srInputSerialization i
835− is' = is {isCompressionType = Just c}
729+ is' = is { isCompressionType = Just c }
836− in i {srInputSerialization = is'}
730+ in i { srInputSerialization = is' }
837
731
838-- | defaultCsvOutput returns OutputSerialization with default CSV
732-- | defaultCsvOutput returns OutputSerialization with default CSV
839-- format.
733-- format.
@@ -850,42 +744,40 @@ defaultJsonOutput = OutputSerializationJSON (JSONOutputProp Nothing)
850-- SelectRequest with the SQL query text given by @query@, the input
744-- SelectRequest with the SQL query text given by @query@, the input
851-- serialization settings (compression format and format information)
745-- serialization settings (compression format and format information)
852-- @inputSer@ and the output serialization settings @outputSer@.
746-- @inputSer@ and the output serialization settings @outputSer@.
853−selectRequest ::
854− Text ->
855− InputSerialization ->
856− OutputSerialization ->
747+selectRequest :: Text -> InputSerialization -> OutputSerialization
857− SelectRequest
748+ -> SelectRequest
858selectRequest sqlQuery inputSer outputSer =
749selectRequest sqlQuery inputSer outputSer =
859− SelectRequest
860− { srExpression = sqlQuery,
750+ SelectRequest { srExpression = sqlQuery
861− srExpressionType = SQL,
751+ , srExpressionType = SQL
862− srInputSerialization = inputSer,
752+ , srInputSerialization = inputSer
863− srOutputSerialization = outputSer,
753+ , srOutputSerialization = outputSer
864− srRequestProgressEnabled = Nothing
754+ , srRequestProgressEnabled = Nothing
865− }
755+ }
866
756
867-- | setRequestProgressEnabled sets the flag for turning on progress
757-- | setRequestProgressEnabled sets the flag for turning on progress
868-- messages when the Select response is being streamed back to the
758-- messages when the Select response is being streamed back to the
869-- client.
759-- client.
870setRequestProgressEnabled :: Bool -> SelectRequest -> SelectRequest
760setRequestProgressEnabled :: Bool -> SelectRequest -> SelectRequest
871setRequestProgressEnabled enabled sr =
761setRequestProgressEnabled enabled sr =
872− sr {srRequestProgressEnabled = Just enabled}
762+ sr { srRequestProgressEnabled = Just enabled }
873
763
874type CSVInputProp = CSVProp
764type CSVInputProp = CSVProp
875
765
876-- | CSVProp represents CSV format properties. It is built up using
766-- | CSVProp represents CSV format properties. It is built up using
877-- the Monoid instance.
767-- the Monoid instance.
878−newtype CSVProp = CSVProp (H.HashMap Text Text)
768+data CSVProp = CSVProp (H.HashMap Text Text)
879− deriving stock (Show, Eq)
769+ deriving (Eq, Show)
880
770
771+#if (__GLASGOW_HASKELL__ >= 804)
881instance Semigroup CSVProp where
772instance Semigroup CSVProp where
882− (CSVProp a) <> (CSVProp b) = CSVProp (b <> a)
773+ (CSVProp a) <> (CSVProp b) = CSVProp (b <> a)
774+#endif
883
775
884instance Monoid CSVProp where
776instance Monoid CSVProp where
885− mempty = CSVProp mempty
777+ mempty = CSVProp mempty
886−
778+#if (__GLASGOW_HASKELL__ < 804)
887−csvPropsList :: CSVProp -> [(Text, Text)]
779+ mappend (CSVProp a) (CSVProp b) = CSVProp (b <> a)
888−csvPropsList (CSVProp h) = sort $ H.toList h
780+#endif
889
781
890defaultCSVProp :: CSVProp
782defaultCSVProp :: CSVProp
891defaultCSVProp = mempty
783defaultCSVProp = mempty
@@ -909,21 +801,18 @@ quoteEscapeCharacter = CSVProp . H.singleton "QuoteEscapeCharacter"
909-- | FileHeaderInfo specifies information about column headers for CSV
801-- | FileHeaderInfo specifies information about column headers for CSV
910-- format.
802-- format.
911data FileHeaderInfo
803data FileHeaderInfo
912− = -- | No column headers are present
804+ = FileHeaderNone -- ^ No column headers are present
913− FileHeaderNone
914− | -- | Headers are present and they should be used
805+ | FileHeaderUse -- ^ Headers are present and they should be used
915− FileHeaderUse
916− | -- | Header are present, but should be ignored
806+ | FileHeaderIgnore -- ^ Header are present, but should be ignored
917− FileHeaderIgnore
918− deriving stock (Show, Eq)
807+ deriving (Eq, Show)
919
808
920-- | Specify the CSV file header info property.
809-- | Specify the CSV file header info property.
921fileHeaderInfo :: FileHeaderInfo -> CSVProp
810fileHeaderInfo :: FileHeaderInfo -> CSVProp
922−fileHeaderInfo = CSVProp . H.singleton "FileHeaderInfo" . toStr
811+fileHeaderInfo = CSVProp . H.singleton "FileHeaderInfo" . toString
923 where
812 where
924− toStr FileHeaderNone = "NONE"
813+ toString FileHeaderNone = "NONE"
925− toStr FileHeaderUse = "USE"
814+ toString FileHeaderUse = "USE"
926− toStr FileHeaderIgnore = "IGNORE"
815+ toString FileHeaderIgnore = "IGNORE"
927
816
928-- | Specify the CSV comment character property. Lines starting with
817-- | Specify the CSV comment character property. Lines starting with
929-- this character are ignored by the server.
818-- this character are ignored by the server.
@@ -936,69 +825,64 @@ allowQuotedRecordDelimiter = CSVProp $ H.singleton "AllowQuotedRecordDelimiter"
936
825
937-- | Set the CSV format properties in the InputSerialization.
826-- | Set the CSV format properties in the InputSerialization.
938setInputCSVProps :: CSVProp -> InputSerialization -> InputSerialization
827setInputCSVProps :: CSVProp -> InputSerialization -> InputSerialization
939−setInputCSVProps p is = is {isFormatInfo = InputFormatCSV p}
828+setInputCSVProps p is = is { isFormatInfo = InputFormatCSV p }
940
829
941-- | Set the CSV format properties in the OutputSerialization.
830-- | Set the CSV format properties in the OutputSerialization.
942outputCSVFromProps :: CSVProp -> OutputSerialization
831outputCSVFromProps :: CSVProp -> OutputSerialization
943−outputCSVFromProps = OutputSerializationCSV
832+outputCSVFromProps p = OutputSerializationCSV p
944
833
945−newtype JSONInputProp = JSONInputProp {jsonipType :: JSONType}
834+data JSONInputProp = JSONInputProp { jsonipType :: JSONType }
946− deriving stock (Show, Eq)
835+ deriving (Eq, Show)
947
836
948data JSONType = JSONTypeDocument | JSONTypeLines
837data JSONType = JSONTypeDocument | JSONTypeLines
949− deriving stock (Show, Eq)
838+ deriving (Eq, Show)
839+
950
840
951-- | OutputSerialization represents output serialization settings for
841-- | OutputSerialization represents output serialization settings for
952-- the SelectRequest. Use `defaultCsvOutput` or `defaultJsonOutput` as
842-- the SelectRequest. Use `defaultCsvOutput` or `defaultJsonOutput` as
953-- a starting point.
843-- a starting point.
954−data OutputSerialization
955− = OutputSerializationJSON JSONOutputProp
844+data OutputSerialization = OutputSerializationJSON JSONOutputProp
956− | OutputSerializationCSV CSVOutputProp
845+ | OutputSerializationCSV CSVOutputProp
957− deriving stock (Show, Eq)
846+ deriving (Eq, Show)
958
847
959type CSVOutputProp = CSVProp
848type CSVOutputProp = CSVProp
960
849
961-- | quoteFields is an output serialization parameter
850-- | quoteFields is an output serialization parameter
962quoteFields :: QuoteFields -> CSVProp
851quoteFields :: QuoteFields -> CSVProp
963−quoteFields q = CSVProp $
964− H.singleton "QuoteFields" $
852+quoteFields q = CSVProp $ H.singleton "QuoteFields" $
965− case q of
853+ case q of
966− QuoteFieldsAsNeeded -> "ASNEEDED"
854+ QuoteFieldsAsNeeded -> "ASNEEDED"
967− QuoteFieldsAlways -> "ALWAYS"
855+ QuoteFieldsAlways -> "ALWAYS"
968
856
969-- | Represent the QuoteField setting.
857-- | Represent the QuoteField setting.
970data QuoteFields = QuoteFieldsAsNeeded | QuoteFieldsAlways
858data QuoteFields = QuoteFieldsAsNeeded | QuoteFieldsAlways
971− deriving stock (Show, Eq)
859+ deriving (Eq, Show)
972
860
973−newtype JSONOutputProp = JSONOutputProp {jsonopRecordDelimiter :: Maybe Text}
861+data JSONOutputProp = JSONOutputProp { jsonopRecordDelimiter :: Maybe Text }
974− deriving stock (Show, Eq)
862+ deriving (Eq, Show)
975
863
976-- | Set the output record delimiter for JSON format
864-- | Set the output record delimiter for JSON format
977outputJSONFromRecordDelimiter :: Text -> OutputSerialization
865outputJSONFromRecordDelimiter :: Text -> OutputSerialization
978outputJSONFromRecordDelimiter t =
866outputJSONFromRecordDelimiter t =
979− OutputSerializationJSON (JSONOutputProp $ Just t)
867+ OutputSerializationJSON (JSONOutputProp $ Just t)
980
868
981-- Response related types
869-- Response related types
982
870
983-- | An EventMessage represents each kind of message received from the server.
871-- | An EventMessage represents each kind of message received from the server.
984−data EventMessage
985− = ProgressEventMessage Progress
872+data EventMessage = ProgressEventMessage { emProgress :: Progress }
986− | StatsEventMessage Stats
873+ | StatsEventMessage { emStats :: Stats }
987− | RequestLevelErrorMessage
874+ | RequestLevelErrorMessage { emErrorCode :: Text
988− Text
875+ , emErrorMessage :: Text
989− -- ^ Error code
876+ }
990− Text
991− -- ^ Error message
992− | RecordPayloadEventMessage ByteString
877+ | RecordPayloadEventMessage { emPayloadBytes :: ByteString }
993− deriving stock (Show, Eq)
878+ deriving (Eq, Show)
994−
879+
995−data MsgHeaderName
880+data MsgHeaderName = MessageType
996− = MessageType
997− | EventType
881+ | EventType
998− | ContentType
882+ | ContentType
999− | ErrorCode
883+ | ErrorCode
1000− | ErrorMessage
884+ | ErrorMessage
1001− deriving stock (Show, Eq)
885+ deriving (Eq, Show)
1002
886
1003msgHeaderValueType :: Word8
887msgHeaderValueType :: Word8
1004msgHeaderValueType = 7
888msgHeaderValueType = 7
@@ -1006,12 +890,11 @@ msgHeaderValueType = 7
1006type MessageHeader = (MsgHeaderName, Text)
890type MessageHeader = (MsgHeaderName, Text)
1007
891
1008-- | Represent the progress event returned in the Select response.
892-- | Represent the progress event returned in the Select response.
1009−data Progress = Progress
893+data Progress = Progress { pBytesScanned :: Int64
1010− { pBytesScanned :: Int64,
1011− pBytesProcessed :: Int64,
894+ , pBytesProcessed :: Int64
1012− pBytesReturned :: Int64
895+ , pBytesReturned :: Int64
1013− }
896+ }
1014− deriving stock (Show, Eq)
897+ deriving (Eq, Show)
1015
898
1016-- | Represent the stats event returned at the end of the Select
899-- | Represent the stats event returned at the end of the Select
1017-- response.
900-- response.
@@ -1024,89 +907,86 @@ type Stats = Progress
1024-- | Represents different kinds of payload that are used with S3 API
907-- | Represents different kinds of payload that are used with S3 API
1025-- requests.
908-- requests.
1026data Payload
909data Payload
1027− = PayloadBS ByteString
910+ = PayloadBS ByteString
1028− | PayloadH Handle Int64 Int64 -- file handle, offset and length
911+ | PayloadH Handle Int64 Int64 -- file handle, offset and length
1029− | PayloadC Int64 (C.ConduitT () ByteString (ResourceT IO) ()) -- length and byte source
912+ | PayloadC Int64 (C.ConduitT () ByteString (ResourceT IO) ()) -- length and byte source
1030
913
1031defaultPayload :: Payload
914defaultPayload :: Payload
1032defaultPayload = PayloadBS ""
915defaultPayload = PayloadBS ""
1033
916
1034−data AdminReqInfo = AdminReqInfo
917+data AdminReqInfo = AdminReqInfo {
1035− { ariMethod :: Method,
918+ ariMethod :: Method
1036− ariPayloadHash :: Maybe ByteString,
919+ , ariPayloadHash :: Maybe ByteString
1037− ariPayload :: Payload,
920+ , ariPayload :: Payload
1038− ariPath :: ByteString,
921+ , ariPath :: ByteString
1039− ariHeaders :: [Header],
922+ , ariHeaders :: [Header]
1040− ariQueryParams :: Query
923+ , ariQueryParams :: Query
1041 }
924 }
1042
925
1043data S3ReqInfo = S3ReqInfo
926data S3ReqInfo = S3ReqInfo
1044− { riMethod :: Method,
927+ { riMethod :: Method
1045− riBucket :: Maybe Bucket,
928+ , riBucket :: Maybe Bucket
1046− riObject :: Maybe Object,
929+ , riObject :: Maybe Object
1047− riQueryParams :: Query,
930+ , riQueryParams :: Query
1048− riHeaders :: [Header],
931+ , riHeaders :: [Header]
1049− riPayload :: Payload,
932+ , riPayload :: Payload
1050− riPayloadHash :: Maybe ByteString,
933+ , riPayloadHash :: Maybe ByteString
1051− riRegion :: Maybe Region,
934+ , riRegion :: Maybe Region
1052− riNeedsLocation :: Bool,
935+ , riNeedsLocation :: Bool
1053− riPresignExpirySecs :: Maybe UrlExpiry
1054 }
936 }
1055
937
1056defaultS3ReqInfo :: S3ReqInfo
938defaultS3ReqInfo :: S3ReqInfo
1057−defaultS3ReqInfo =
939+defaultS3ReqInfo = S3ReqInfo HT.methodGet Nothing Nothing
1058− S3ReqInfo
1059− HT.methodGet
1060− Nothing
1061− Nothing
1062− []
1063− []
1064− defaultPayload
940+ [] [] defaultPayload Nothing Nothing True
1065− Nothing
1066− Nothing
1067− True
1068− Nothing
1069
941
1070getS3Path :: Maybe Bucket -> Maybe Object -> ByteString
942getS3Path :: Maybe Bucket -> Maybe Object -> ByteString
1071getS3Path b o =
943getS3Path b o =
1072− let segments = map encodeUtf8 $ catMaybes $ b : bool [] [o] (isJust b)
944+ let segments = map toS $ catMaybes $ b : bool [] [o] (isJust b)
945+ in
1073− in B.concat ["/", B.intercalate "/" segments]
946+ B.concat ["/", B.intercalate "/" segments]
947+
948+-- | Time to expire for a presigned URL. It interpreted as a number of
949+-- seconds. The maximum duration that can be specified is 7 days.
950+type UrlExpiry = Int
1074
951
1075type RegionMap = H.HashMap Bucket Region
952type RegionMap = H.HashMap Bucket Region
1076
953
1077-- | The Minio Monad - all computations accessing object storage
954-- | The Minio Monad - all computations accessing object storage
1078-- happens in it.
955-- happens in it.
1079−newtype Minio a = Minio
956+newtype Minio a = Minio {
1080− { unMinio :: ReaderT MinioConn (ResourceT IO) a
957+ unMinio :: ReaderT MinioConn (ResourceT IO) a
1081 }
958 }
1082− deriving newtype
959+ deriving (
1083− ( Functor,
960+ Functor
1084− Applicative,
961+ , Applicative
1085− Monad,
962+ , Monad
1086− MonadIO,
963+ , MonadIO
1087− MonadReader MinioConn,
964+ , MonadReader MinioConn
1088− MonadResource,
965+ , MonadResource
1089− MonadUnliftIO
1090 )
966 )
1091
967
968+instance MonadUnliftIO Minio where
969+ askUnliftIO = Minio $ ReaderT $ \r ->
970+ withUnliftIO $ \u ->
971+ return (UnliftIO (unliftIO u . flip runReaderT r . unMinio))
972+
1092-- | MinioConn holds connection info and a connection pool to allow
973-- | MinioConn holds connection info and a connection pool to allow
1093-- for efficient resource re-use.
974-- for efficient resource re-use.
1094data MinioConn = MinioConn
975data MinioConn = MinioConn
1095− { mcConnInfo :: ConnectInfo,
976+ { mcConnInfo :: ConnectInfo
1096− mcConnManager :: NC.Manager,
977+ , mcConnManager :: NC.Manager
1097− mcRegionMap :: MVar RegionMap
978+ , mcRegionMap :: MVar RegionMap
1098 }
979 }
1099
980
1100class HasSvcNamespace env where
981class HasSvcNamespace env where
1101 getSvcNamespace :: env -> Text
982 getSvcNamespace :: env -> Text
1102
983
1103instance HasSvcNamespace MinioConn where
984instance HasSvcNamespace MinioConn where
1104− getSvcNamespace env =
1105− let host = connectHost $ mcConnInfo env
985+ getSvcNamespace env = let host = connectHost $ mcConnInfo env
1106− in ( if host == "storage.googleapis.com"
986+ in if | host == "storage.googleapis.com" ->
1107− then "http://doc.s3.amazonaws.com/2006-03-01"
987+ "http://doc.s3.amazonaws.com/2006-03-01"
988+ | otherwise ->
1108− else "http://s3.amazonaws.com/doc/2006-03-01/"
989+ "http://s3.amazonaws.com/doc/2006-03-01/"
1109− )
1110
990
1111-- | Takes connection information and returns a connection object to
991-- | Takes connection information and returns a connection object to
1112-- be passed to 'runMinio'. The returned value can be kept in the
992-- be passed to 'runMinio'. The returned value can be kept in the
@@ -1114,16 +994,11 @@ instance HasSvcNamespace MinioConn where
1114-- object storage is accessed.
994-- object storage is accessed.
1115connect :: ConnectInfo -> IO MinioConn
995connect :: ConnectInfo -> IO MinioConn
1116connect ci = do
996connect ci = do
1117− let settings
1118− | connectIsSecure ci && connectDisableTLSCertValidation ci =
997+ let settings | connectIsSecure ci && connectDisableTLSCertValidation ci =
1119− let badTlsSettings =
1120− case def of
1121− tlsSettings@Conn.TLSSettingsSimple {} ->
998+ let badTlsSettings = Conn.TLSSettingsSimple True False False
1122− tlsSettings {Conn.settingDisableCertificateValidation = True}
1123− _ -> def
1124− in TLS.mkManagerSettings badTlsSettings Nothing
999+ in TLS.mkManagerSettings badTlsSettings Nothing
1125− | connectIsSecure ci = NC.tlsManagerSettings
1000+ | connectIsSecure ci = NC.tlsManagerSettings
1126− | otherwise = defaultManagerSettings
1001+ | otherwise = defaultManagerSettings
1127 mgr <- NC.newManager settings
1002 mgr <- NC.newManager settings
1128 mkMinioConn ci mgr
1003 mkMinioConn ci mgr
1129
1004
@@ -1137,8 +1012,8 @@ runMinioWith conn m = runResourceT $ runMinioResWith conn m
1137-- `MinioConn`.
1012-- `MinioConn`.
1138mkMinioConn :: ConnectInfo -> NC.Manager -> IO MinioConn
1013mkMinioConn :: ConnectInfo -> NC.Manager -> IO MinioConn
1139mkMinioConn ci mgr = do
1014mkMinioConn ci mgr = do
1140− rMapMVar <- M.newMVar H.empty
1015+ rMapMVar <- M.newMVar H.empty
1141− return $ MinioConn ci mgr rMapMVar
1016+ return $ MinioConn ci mgr rMapMVar
1142
1017
1143-- | Run the Minio action and return the result or an error.
1018-- | Run the Minio action and return the result or an error.
1144runMinio :: ConnectInfo -> Minio a -> IO (Either MinioErr a)
1019runMinio :: ConnectInfo -> Minio a -> IO (Either MinioErr a)
@@ -1151,12 +1026,12 @@ runMinio ci m = do
1151runMinioResWith :: MinioConn -> Minio a -> ResourceT IO (Either MinioErr a)
1026runMinioResWith :: MinioConn -> Minio a -> ResourceT IO (Either MinioErr a)
1152runMinioResWith conn m =
1027runMinioResWith conn m =
1153 flip runReaderT conn . unMinio $
1028 flip runReaderT conn . unMinio $
1154− fmap Right m
1029+ fmap Right m `U.catches`
1155− `U.catches` [ U.Handler handlerServiceErr,
1030+ [ U.Handler handlerServiceErr
1156− U.Handler handlerHE,
1031+ , U.Handler handlerHE
1157− U.Handler handlerFE,
1032+ , U.Handler handlerFE
1158− U.Handler handlerValidation
1033+ , U.Handler handlerValidation
1159− ]
1034+ ]
1160 where
1035 where
1161 handlerServiceErr = return . Left . MErrService
1036 handlerServiceErr = return . Left . MErrService
1162 handlerHE = return . Left . MErrHTTP
1037 handlerHE = return . Left . MErrHTTP
@@ -1170,22 +1045,9 @@ runMinioRes ci m = do
1170 conn <- liftIO $ connect ci
1045 conn <- liftIO $ connect ci
1171 runMinioResWith conn m
1046 runMinioResWith conn m
1172
1047
1048+s3Name :: Text -> Text -> Name
1049+s3Name ns s = Name s (Just ns) Nothing
1050+
1173-- | Format as per RFC 1123.
1051-- | Format as per RFC 1123.
1174formatRFC1123 :: UTCTime -> T.Text
1052formatRFC1123 :: UTCTime -> T.Text
1175formatRFC1123 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %X %Z"
1053formatRFC1123 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %X %Z"
1176−
1177−lookupRegionCache :: Bucket -> Minio (Maybe Region)
1178−lookupRegionCache b = do
1179− rMVar <- asks mcRegionMap
1180− rMap <- UM.readMVar rMVar
1181− return $ H.lookup b rMap
1182−
1183−addToRegionCache :: Bucket -> Region -> Minio ()
1184−addToRegionCache b region = do
1185− rMVar <- asks mcRegionMap
1186− UM.modifyMVar_ rMVar $ return . H.insert b region
1187−
1188−deleteFromRegionCache :: Bucket -> Minio ()
1189−deleteFromRegionCache b = do
1190− rMVar <- asks mcRegionMap
1191− UM.modifyMVar_ rMVar $ return . H.delete b
src/Network/Minio/Data/ByteString.hs+18−19
@@ -13,21 +13,23 @@
13-- See the License for the specific language governing permissions and
13-- See the License for the specific language governing permissions and
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16−{-# LANGUAGE FlexibleInstances #-}
17
16
17+{-# LANGUAGE FlexibleInstances #-}
18module Network.Minio.Data.ByteString
18module Network.Minio.Data.ByteString
19+ (
19− ( stripBS,
20+ stripBS
20− UriEncodable (..),
21+ , UriEncodable(..)
21− )
22−where
22+ ) where
23
23
24import qualified Data.ByteString as B
24import qualified Data.ByteString as B
25import qualified Data.ByteString.Builder as BB
25import qualified Data.ByteString.Builder as BB
26import qualified Data.ByteString.Char8 as BC8
26import qualified Data.ByteString.Char8 as BC8
27import qualified Data.ByteString.Lazy as LB
27import qualified Data.ByteString.Lazy as LB
28−import Data.Char (isAsciiLower, isAsciiUpper, isDigit, isSpace, toUpper)
28+import Data.Char (isSpace, toUpper, isAsciiUpper, isAsciiLower, isDigit)
29import qualified Data.Text as T
29import qualified Data.Text as T
30−import Numeric (showHex)
30+import Numeric (showHex)
31+
32+import Lib.Prelude
31
33
32stripBS :: ByteString -> ByteString
34stripBS :: ByteString -> ByteString
33stripBS = BC8.dropWhile isSpace . fst . BC8.spanEnd isSpace
35stripBS = BC8.dropWhile isSpace . fst . BC8.spanEnd isSpace
@@ -37,10 +39,8 @@ class UriEncodable s where
37
39
38instance UriEncodable [Char] where
40instance UriEncodable [Char] where
39 uriEncode encodeSlash payload =
41 uriEncode encodeSlash payload =
40− LB.toStrict $
41− BB.toLazyByteString $
42+ LB.toStrict $ BB.toLazyByteString $ mconcat $
42− mconcat $
43− map (`uriEncodeChar` encodeSlash) payload
43+ map (`uriEncodeChar` encodeSlash) payload
44
44
45instance UriEncodable ByteString where
45instance UriEncodable ByteString where
46 -- assumes that uriEncode is passed ASCII encoded strings.
46 -- assumes that uriEncode is passed ASCII encoded strings.
@@ -59,17 +59,16 @@ uriEncodeChar '/' True = BB.byteString "%2F"
59uriEncodeChar '/' False = BB.char7 '/'
59uriEncodeChar '/' False = BB.char7 '/'
60uriEncodeChar ch _
60uriEncodeChar ch _
61 | isAsciiUpper ch
61 | isAsciiUpper ch
62− || isAsciiLower ch
62+ || isAsciiLower ch
63− || isDigit ch
63+ || isDigit ch
64− || (ch == '_')
64+ || (ch == '_')
65− || (ch == '-')
65+ || (ch == '-')
66− || (ch == '.')
66+ || (ch == '.')
67− || (ch == '~') =
68− BB.char7 ch
67+ || (ch == '~') = BB.char7 ch
69 | otherwise = mconcat $ map f $ B.unpack $ encodeUtf8 $ T.singleton ch
68 | otherwise = mconcat $ map f $ B.unpack $ encodeUtf8 $ T.singleton ch
70 where
69 where
71 f :: Word8 -> BB.Builder
70 f :: Word8 -> BB.Builder
72 f n = BB.char7 '%' <> BB.string7 hexStr
71 f n = BB.char7 '%' <> BB.string7 hexStr
73 where
72 where
74 hexStr = map toUpper $ showHex q $ showHex r ""
73 hexStr = map toUpper $ showHex q $ showHex r ""
75− (q, r) = divMod n (16 :: Word8)
74+ (q, r) = divMod (fromIntegral n) (16::Word8)
src/Network/Minio/Data/Crypto.hs+33−32
@@ -15,54 +15,55 @@
15--
15--
16
16
17module Network.Minio.Data.Crypto
17module Network.Minio.Data.Crypto
18+ (
18− ( hashSHA256,
19+ hashSHA256
19− hashSHA256FromSource,
20+ , hashSHA256FromSource
21+
20− hashMD5,
22+ , hashMD5
21− hashMD5ToBase64,
23+ , hashMD5ToBase64
22− hashMD5FromSource,
24+ , hashMD5FromSource
25+
23− hmacSHA256,
26+ , hmacSHA256
24− hmacSHA256RawBS,
27+ , hmacSHA256RawBS
25− digestToBS,
28+ , digestToBS
26− digestToBase16,
29+ , digestToBase16
30+
27− encodeToBase64,
31+ , encodeToBase64
28− )
29−where
32+ ) where
30−
33+
31−import Crypto.Hash
34+import Crypto.Hash (Digest, MD5 (..), SHA256 (..),
32− ( Digest,
33− MD5 (..),
34− SHA256 (..),
35− hashWith,
35+ hashWith)
36− )
37−import Crypto.Hash.Conduit (sinkHash)
36+import Crypto.Hash.Conduit (sinkHash)
38−import Crypto.MAC.HMAC (HMAC, hmac)
37+import Crypto.MAC.HMAC (HMAC, hmac)
39−import Data.ByteArray (ByteArrayAccess, convert)
38+import Data.ByteArray (ByteArrayAccess, convert)
40−import Data.ByteArray.Encoding (Base (Base16, Base64), convertToBase)
39+import Data.ByteArray.Encoding (Base (Base16, Base64), convertToBase)
41−import qualified Data.Conduit as C
40+import qualified Data.Conduit as C
41+
42+import Lib.Prelude
42
43
43hashSHA256 :: ByteString -> ByteString
44hashSHA256 :: ByteString -> ByteString
44hashSHA256 = digestToBase16 . hashWith SHA256
45hashSHA256 = digestToBase16 . hashWith SHA256
45
46
46−hashSHA256FromSource :: (Monad m) => C.ConduitM () ByteString m () -> m ByteString
47+hashSHA256FromSource :: Monad m => C.ConduitM () ByteString m () -> m ByteString
47hashSHA256FromSource src = do
48hashSHA256FromSource src = do
48 digest <- C.connect src sinkSHA256Hash
49 digest <- C.connect src sinkSHA256Hash
49 return $ digestToBase16 digest
50 return $ digestToBase16 digest
50 where
51 where
51 -- To help with type inference
52 -- To help with type inference
52− sinkSHA256Hash :: (Monad m) => C.ConduitM ByteString Void m (Digest SHA256)
53+ sinkSHA256Hash :: Monad m => C.ConduitM ByteString Void m (Digest SHA256)
53 sinkSHA256Hash = sinkHash
54 sinkSHA256Hash = sinkHash
54
55
55-- Returns MD5 hash hex encoded.
56-- Returns MD5 hash hex encoded.
56hashMD5 :: ByteString -> ByteString
57hashMD5 :: ByteString -> ByteString
57hashMD5 = digestToBase16 . hashWith MD5
58hashMD5 = digestToBase16 . hashWith MD5
58
59
59−hashMD5FromSource :: (Monad m) => C.ConduitM () ByteString m () -> m ByteString
60+hashMD5FromSource :: Monad m => C.ConduitM () ByteString m () -> m ByteString
60hashMD5FromSource src = do
61hashMD5FromSource src = do
61 digest <- C.connect src sinkMD5Hash
62 digest <- C.connect src sinkMD5Hash
62 return $ digestToBase16 digest
63 return $ digestToBase16 digest
63 where
64 where
64 -- To help with type inference
65 -- To help with type inference
65− sinkMD5Hash :: (Monad m) => C.ConduitM ByteString Void m (Digest MD5)
66+ sinkMD5Hash :: Monad m => C.ConduitM ByteString Void m (Digest MD5)
66 sinkMD5Hash = sinkHash
67 sinkMD5Hash = sinkHash
67
68
68hmacSHA256 :: ByteString -> ByteString -> HMAC SHA256
69hmacSHA256 :: ByteString -> ByteString -> HMAC SHA256
@@ -71,15 +72,15 @@ hmacSHA256 message key = hmac key message
71hmacSHA256RawBS :: ByteString -> ByteString -> ByteString
72hmacSHA256RawBS :: ByteString -> ByteString -> ByteString
72hmacSHA256RawBS message key = convert $ hmacSHA256 message key
73hmacSHA256RawBS message key = convert $ hmacSHA256 message key
73
74
74−digestToBS :: (ByteArrayAccess a) => a -> ByteString
75+digestToBS :: ByteArrayAccess a => a -> ByteString
75digestToBS = convert
76digestToBS = convert
76
77
77−digestToBase16 :: (ByteArrayAccess a) => a -> ByteString
78+digestToBase16 :: ByteArrayAccess a => a -> ByteString
78digestToBase16 = convertToBase Base16
79digestToBase16 = convertToBase Base16
79
80
80-- Returns MD5 hash base 64 encoded.
81-- Returns MD5 hash base 64 encoded.
81−hashMD5ToBase64 :: (ByteArrayAccess a) => a -> ByteString
82+hashMD5ToBase64 :: ByteArrayAccess a => a -> ByteString
82hashMD5ToBase64 = convertToBase Base64 . hashWith MD5
83hashMD5ToBase64 = convertToBase Base64 . hashWith MD5
83
84
84−encodeToBase64 :: (ByteArrayAccess a) => a -> ByteString
85+encodeToBase64 :: ByteArrayAccess a => a -> ByteString
85encodeToBase64 = convertToBase Base64
86encodeToBase64 = convertToBase Base64
src/Network/Minio/Data/Time.hs+13−17
@@ -15,24 +15,20 @@
15--
15--
16
16
17module Network.Minio.Data.Time
17module Network.Minio.Data.Time
18+ (
18− ( awsTimeFormat,
19+ awsTimeFormat
19− awsTimeFormatBS,
20+ , awsTimeFormatBS
20− awsDateFormat,
21+ , awsDateFormat
21− awsDateFormatBS,
22+ , awsDateFormatBS
22− awsParseTime,
23+ , awsParseTime
23− iso8601TimeFormat,
24+ , iso8601TimeFormat
24− UrlExpiry,
25− )
26−where
25+ ) where
27−
26+
27+
28−import Data.ByteString.Char8 (pack)
28+import Data.ByteString.Char8 (pack)
29import qualified Data.Time as Time
29import qualified Data.Time as Time
30−import Data.Time.Format.ISO8601 (iso8601Show)
31−import Lib.Prelude
32
30
33−-- | Time to expire for a presigned URL. It interpreted as a number of
31+import Lib.Prelude
34−-- seconds. The maximum duration that can be specified is 7 days.
35−type UrlExpiry = Int
36
32
37awsTimeFormat :: UTCTime -> [Char]
33awsTimeFormat :: UTCTime -> [Char]
38awsTimeFormat = Time.formatTime Time.defaultTimeLocale "%Y%m%dT%H%M%SZ"
34awsTimeFormat = Time.formatTime Time.defaultTimeLocale "%Y%m%dT%H%M%SZ"
@@ -50,4 +46,4 @@ awsParseTime :: [Char] -> Maybe UTCTime
50awsParseTime = Time.parseTimeM False Time.defaultTimeLocale "%Y%m%dT%H%M%SZ"
46awsParseTime = Time.parseTimeM False Time.defaultTimeLocale "%Y%m%dT%H%M%SZ"
51
47
52iso8601TimeFormat :: UTCTime -> [Char]
48iso8601TimeFormat :: UTCTime -> [Char]
53−iso8601TimeFormat = iso8601Show
49+iso8601TimeFormat = Time.formatTime Time.defaultTimeLocale (Time.iso8601DateFormat $ Just "%T%QZ")
src/Network/Minio/Errors.hs+52−59
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -14,83 +14,76 @@
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16
16
17−module Network.Minio.Errors
17+module Network.Minio.Errors where
18− ( MErrV (..),
19− ServiceErr (..),
20− MinioErr (..),
21− toServiceErr,
22− )
23−where
24
18
25−import Control.Exception (IOException)
19+import Control.Exception
26import qualified Network.HTTP.Conduit as NC
20import qualified Network.HTTP.Conduit as NC
27
21
22+import Lib.Prelude
23+
24+
28---------------------------------
25---------------------------------
29-- Errors
26-- Errors
30---------------------------------
27---------------------------------
31−
32-- | Various validation errors
28-- | Various validation errors
33−data MErrV
34− = MErrVSinglePUTSizeExceeded Int64
29+data MErrV = MErrVSinglePUTSizeExceeded Int64
35− | MErrVPutSizeExceeded Int64
30+ | MErrVPutSizeExceeded Int64
36− | MErrVETagHeaderNotFound
31+ | MErrVETagHeaderNotFound
37− | MErrVInvalidObjectInfoResponse
32+ | MErrVInvalidObjectInfoResponse
38− | MErrVInvalidSrcObjSpec Text
33+ | MErrVInvalidSrcObjSpec Text
39− | MErrVInvalidSrcObjByteRange (Int64, Int64)
34+ | MErrVInvalidSrcObjByteRange (Int64, Int64)
40− | MErrVCopyObjSingleNoRangeAccepted
35+ | MErrVCopyObjSingleNoRangeAccepted
41− | MErrVRegionNotSupported Text
36+ | MErrVRegionNotSupported Text
42− | MErrVXmlParse Text
37+ | MErrVXmlParse Text
43− | MErrVInvalidBucketName Text
38+ | MErrVInvalidBucketName Text
44− | MErrVInvalidObjectName Text
39+ | MErrVInvalidObjectName Text
45− | MErrVInvalidUrlExpiry Int
40+ | MErrVInvalidUrlExpiry Int
46− | MErrVJsonParse Text
41+ | MErrVJsonParse Text
47− | MErrVInvalidHealPath
42+ | MErrVInvalidHealPath
48− | MErrVMissingCredentials
43+ | MErrVMissingCredentials
49− | MErrVInvalidEncryptionKeyLength
44+ | MErrVInvalidEncryptionKeyLength
50− | MErrVStreamingBodyUnexpectedEOF
45+ | MErrVStreamingBodyUnexpectedEOF
51− | MErrVUnexpectedPayload
46+ | MErrVUnexpectedPayload
52− | MErrVSTSEndpointNotFound
53− deriving stock (Show, Eq)
47+ deriving (Show, Eq)
54
48
55instance Exception MErrV
49instance Exception MErrV
56
50
57-- | Errors returned by S3 compatible service
51-- | Errors returned by S3 compatible service
58−data ServiceErr
59− = BucketAlreadyExists
52+data ServiceErr = BucketAlreadyExists
60− | BucketAlreadyOwnedByYou
53+ | BucketAlreadyOwnedByYou
61− | NoSuchBucket
54+ | NoSuchBucket
62− | InvalidBucketName
55+ | InvalidBucketName
63− | NoSuchKey
56+ | NoSuchKey
64− | SelectErr Text Text
57+ | SelectErr Text Text
65− | ServiceErr Text Text
58+ | ServiceErr Text Text
66− deriving stock (Show, Eq)
59+ deriving (Show, Eq)
67
60
68instance Exception ServiceErr
61instance Exception ServiceErr
69
62
70toServiceErr :: Text -> Text -> ServiceErr
63toServiceErr :: Text -> Text -> ServiceErr
71−toServiceErr "NoSuchKey" _ = NoSuchKey
64+toServiceErr "NoSuchKey" _ = NoSuchKey
72−toServiceErr "NoSuchBucket" _ = NoSuchBucket
65+toServiceErr "NoSuchBucket" _ = NoSuchBucket
73−toServiceErr "InvalidBucketName" _ = InvalidBucketName
66+toServiceErr "InvalidBucketName" _ = InvalidBucketName
74toServiceErr "BucketAlreadyOwnedByYou" _ = BucketAlreadyOwnedByYou
67toServiceErr "BucketAlreadyOwnedByYou" _ = BucketAlreadyOwnedByYou
75−toServiceErr "BucketAlreadyExists" _ = BucketAlreadyExists
68+toServiceErr "BucketAlreadyExists" _ = BucketAlreadyExists
76−toServiceErr code message = ServiceErr code message
69+toServiceErr code message = ServiceErr code message
70+
77
71
78-- | Errors thrown by the library
72-- | Errors thrown by the library
79−data MinioErr
80− = MErrHTTP NC.HttpException
73+data MinioErr = MErrHTTP NC.HttpException
81− | MErrIO IOException
74+ | MErrIO IOException
82− | MErrService ServiceErr
75+ | MErrService ServiceErr
83− | MErrValidation MErrV
76+ | MErrValidation MErrV
84− deriving stock (Show)
77+ deriving (Show)
85
78
86instance Eq MinioErr where
79instance Eq MinioErr where
87− MErrHTTP _ == MErrHTTP _ = True
80+ MErrHTTP _ == MErrHTTP _ = True
88− MErrHTTP _ == _ = False
81+ MErrHTTP _ == _ = False
89− MErrIO _ == MErrIO _ = True
82+ MErrIO _ == MErrIO _ = True
90− MErrIO _ == _ = False
83+ MErrIO _ == _ = False
91− MErrService a == MErrService b = a == b
84+ MErrService a == MErrService b = a == b
92− MErrService _ == _ = False
85+ MErrService _ == _ = False
93− MErrValidation a == MErrValidation b = a == b
86+ MErrValidation a == MErrValidation b = a == b
94− MErrValidation _ == _ = False
87+ MErrValidation _ == _ = False
95
88
96instance Exception MinioErr
89instance Exception MinioErr
src/Network/Minio/JsonParser.hs+16−23
@@ -15,35 +15,28 @@
15--
15--
16
16
17module Network.Minio.JsonParser
17module Network.Minio.JsonParser
18+ (
18− ( parseErrResponseJSON,
19+ parseErrResponseJSON
19− )
20−where
20+ ) where
21
21
22−import Data.Aeson
22+import Data.Aeson (FromJSON, eitherDecode, parseJSON,
23− ( FromJSON,
24− eitherDecode,
25− parseJSON,
26− withObject,
23+ withObject, (.:))
27− (.:),
28− )
29−import qualified Data.Text as T
24+import qualified Data.Text as T
30−import Lib.Prelude
31−import Network.Minio.Errors
32
25
33−data AdminErrJSON = AdminErrJSON
26+import Lib.Prelude
34− { aeCode :: Text,
35− aeMessage :: Text
36− }
37− deriving stock (Eq, Show)
38
27
28+import Network.Minio.Errors
29+
30+data AdminErrJSON = AdminErrJSON { aeCode :: Text
31+ , aeMessage :: Text
32+ } deriving (Eq, Show)
39instance FromJSON AdminErrJSON where
33instance FromJSON AdminErrJSON where
40− parseJSON = withObject "AdminErrJSON" $ \v ->
34+ parseJSON = withObject "AdminErrJSON" $ \v -> AdminErrJSON
41− AdminErrJSON
42− <$> v .: "Code"
35+ <$> v .: "Code"
43− <*> v .: "Message"
36+ <*> v .: "Message"
44
37
45parseErrResponseJSON :: (MonadIO m) => LByteString -> m ServiceErr
38parseErrResponseJSON :: (MonadIO m) => LByteString -> m ServiceErr
46parseErrResponseJSON jsondata =
39parseErrResponseJSON jsondata =
47 case eitherDecode jsondata of
40 case eitherDecode jsondata of
48 Right aErr -> return $ toServiceErr (aeCode aErr) (aeMessage aErr)
41 Right aErr -> return $ toServiceErr (aeCode aErr) (aeMessage aErr)
49− Left err -> throwIO $ MErrVJsonParse $ T.pack err
42+ Left err -> throwIO $ MErrVJsonParse $ T.pack err
src/Network/Minio/ListOps.hs+38−98
@@ -16,50 +16,20 @@
16
16
17module Network.Minio.ListOps where
17module Network.Minio.ListOps where
18
18
19−import qualified Data.Conduit as C
19+import qualified Data.Conduit as C
20import qualified Data.Conduit.Combinators as CC
20import qualified Data.Conduit.Combinators as CC
21−import qualified Data.Conduit.List as CL
21+import qualified Data.Conduit.List as CL
22+
23+import Lib.Prelude
24+
22−import Network.Minio.Data
25+import Network.Minio.Data
23− ( Bucket,
24− ListObjectsResult
25− ( lorCPrefixes,
26− lorHasMore,
27− lorNextToken,
28− lorObjects
29− ),
30− ListObjectsV1Result
31− ( lorCPrefixes',
32− lorHasMore',
33− lorNextMarker,
34− lorObjects'
35− ),
36− ListPartsResult (lprHasMore, lprNextPart, lprParts),
37− ListUploadsResult
38− ( lurHasMore,
39− lurNextKey,
40− lurNextUpload,
41− lurUploads
42− ),
43− Minio,
44− Object,
45− ObjectInfo,
46− ObjectPartInfo (opiSize),
47− UploadId,
48− UploadInfo (UploadInfo),
49− )
50−import Network.Minio.S3API
26+import Network.Minio.S3API
51− ( listIncompleteParts',
52− listIncompleteUploads',
53− listObjects',
54− listObjectsV1',
55− )
56
27
57-- | Represents a list output item - either an object or an object
28-- | Represents a list output item - either an object or an object
58-- prefix (i.e. a directory).
29-- prefix (i.e. a directory).
59−data ListItem
60− = ListItemObject ObjectInfo
30+data ListItem = ListItemObject ObjectInfo
61− | ListItemPrefix Text
31+ | ListItemPrefix Text
62− deriving stock (Show, Eq)
32+ deriving (Show, Eq)
63
33
64-- | @'listObjects' bucket prefix recurse@ lists objects in a bucket
34-- | @'listObjects' bucket prefix recurse@ lists objects in a bucket
65-- similar to a file system tree traversal.
35-- similar to a file system tree traversal.
@@ -78,103 +48,73 @@ listObjects bucket prefix recurse = loop Nothing
78 where
48 where
79 loop :: Maybe Text -> C.ConduitM () ListItem Minio ()
49 loop :: Maybe Text -> C.ConduitM () ListItem Minio ()
80 loop nextToken = do
50 loop nextToken = do
51+ let
81− let delimiter = bool (Just "/") Nothing recurse
52+ delimiter = bool (Just "/") Nothing recurse
82
53
83 res <- lift $ listObjects' bucket prefix nextToken delimiter Nothing
54 res <- lift $ listObjects' bucket prefix nextToken delimiter Nothing
84 CL.sourceList $ map ListItemObject $ lorObjects res
55 CL.sourceList $ map ListItemObject $ lorObjects res
85 unless recurse $
56 unless recurse $
86− CL.sourceList $
87− map ListItemPrefix $
57+ CL.sourceList $ map ListItemPrefix $ lorCPrefixes res
88− lorCPrefixes res
89 when (lorHasMore res) $
58 when (lorHasMore res) $
90 loop (lorNextToken res)
59 loop (lorNextToken res)
91
60
92-- | Lists objects - similar to @listObjects@, however uses the older
61-- | Lists objects - similar to @listObjects@, however uses the older
93-- V1 AWS S3 API. Prefer @listObjects@ to this.
62-- V1 AWS S3 API. Prefer @listObjects@ to this.
94−listObjectsV1 ::
63+listObjectsV1 :: Bucket -> Maybe Text -> Bool
95− Bucket ->
96− Maybe Text ->
97− Bool ->
98− C.ConduitM () ListItem Minio ()
64+ -> C.ConduitM () ListItem Minio ()
99listObjectsV1 bucket prefix recurse = loop Nothing
65listObjectsV1 bucket prefix recurse = loop Nothing
100 where
66 where
101 loop :: Maybe Text -> C.ConduitM () ListItem Minio ()
67 loop :: Maybe Text -> C.ConduitM () ListItem Minio ()
102 loop nextMarker = do
68 loop nextMarker = do
69+ let
103− let delimiter = bool (Just "/") Nothing recurse
70+ delimiter = bool (Just "/") Nothing recurse
104
71
105 res <- lift $ listObjectsV1' bucket prefix nextMarker delimiter Nothing
72 res <- lift $ listObjectsV1' bucket prefix nextMarker delimiter Nothing
106 CL.sourceList $ map ListItemObject $ lorObjects' res
73 CL.sourceList $ map ListItemObject $ lorObjects' res
107 unless recurse $
74 unless recurse $
108− CL.sourceList $
109− map ListItemPrefix $
75+ CL.sourceList $ map ListItemPrefix $ lorCPrefixes' res
110− lorCPrefixes' res
111 when (lorHasMore' res) $
76 when (lorHasMore' res) $
112 loop (lorNextMarker res)
77 loop (lorNextMarker res)
113
78
114-- | List incomplete uploads in a bucket matching the given prefix. If
79-- | List incomplete uploads in a bucket matching the given prefix. If
115-- recurse is set to True incomplete uploads for the given prefix are
80-- recurse is set to True incomplete uploads for the given prefix are
116-- recursively listed.
81-- recursively listed.
117−listIncompleteUploads ::
82+listIncompleteUploads :: Bucket -> Maybe Text -> Bool
118− Bucket ->
119− Maybe Text ->
120− Bool ->
121− C.ConduitM () UploadInfo Minio ()
83+ -> C.ConduitM () UploadInfo Minio ()
122listIncompleteUploads bucket prefix recurse = loop Nothing Nothing
84listIncompleteUploads bucket prefix recurse = loop Nothing Nothing
123 where
85 where
124 loop :: Maybe Text -> Maybe Text -> C.ConduitM () UploadInfo Minio ()
86 loop :: Maybe Text -> Maybe Text -> C.ConduitM () UploadInfo Minio ()
125 loop nextKeyMarker nextUploadIdMarker = do
87 loop nextKeyMarker nextUploadIdMarker = do
88+ let
126− let delimiter = bool (Just "/") Nothing recurse
89+ delimiter = bool (Just "/") Nothing recurse
127−
90+
128− res <-
129− lift $
130− listIncompleteUploads'
91+ res <- lift $ listIncompleteUploads' bucket prefix delimiter
131− bucket
132− prefix
133− delimiter
134− nextKeyMarker
135− nextUploadIdMarker
92+ nextKeyMarker nextUploadIdMarker Nothing
136− Nothing
93+
137−
138− aggrSizes <- lift $
139− forM (lurUploads res) $ \(uKey, uId, _) -> do
94+ aggrSizes <- lift $ forM (lurUploads res) $ \(uKey, uId, _) -> do
140− partInfos <-
141− C.runConduit $
142− listIncompleteParts bucket uKey uId
95+ partInfos <- C.runConduit $ listIncompleteParts bucket uKey uId
143− C..| CC.sinkList
96+ C..| CC.sinkList
144− return $ foldl' (\sizeSofar p -> opiSize p + sizeSofar) 0 partInfos
97+ return $ foldl (\sizeSofar p -> opiSize p + sizeSofar) 0 partInfos
145
98
146 CL.sourceList $
99 CL.sourceList $
147− zipWith
148− ( curry
149− ( \((uKey, uId, uInitTime), size) ->
100+ map (\((uKey, uId, uInitTime), size) ->
150− UploadInfo uKey uId uInitTime size
101+ UploadInfo uKey uId uInitTime size
151− )
152− )
153− (lurUploads res)
102+ ) $ zip (lurUploads res) aggrSizes
154− aggrSizes
155
103
156 when (lurHasMore res) $
104 when (lurHasMore res) $
157 loop (lurNextKey res) (lurNextUpload res)
105 loop (lurNextKey res) (lurNextUpload res)
158
106
107+
159-- | List object parts of an ongoing multipart upload for given
108-- | List object parts of an ongoing multipart upload for given
160-- bucket, object and uploadId.
109-- bucket, object and uploadId.
161−listIncompleteParts ::
110+listIncompleteParts :: Bucket -> Object -> UploadId
162− Bucket ->
163− Object ->
164− UploadId ->
165− C.ConduitM () ObjectPartInfo Minio ()
111+ -> C.ConduitM () ObjectPartInfo Minio ()
166listIncompleteParts bucket object uploadId = loop Nothing
112listIncompleteParts bucket object uploadId = loop Nothing
167 where
113 where
168 loop :: Maybe Text -> C.ConduitM () ObjectPartInfo Minio ()
114 loop :: Maybe Text -> C.ConduitM () ObjectPartInfo Minio ()
169 loop nextPartMarker = do
115 loop nextPartMarker = do
170− res <-
171− lift $
172− listIncompleteParts'
116+ res <- lift $ listIncompleteParts' bucket object uploadId Nothing
173− bucket
174− object
175− uploadId
176− Nothing
177− nextPartMarker
117+ nextPartMarker
178 CL.sourceList $ lprParts res
118 CL.sourceList $ lprParts res
179 when (lprHasMore res) $
119 when (lprHasMore res) $
180 loop (show <$> lprNextPart res)
120 loop (show <$> lprNextPart res)
src/Network/Minio/PresignedOperations.hs+152−215
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -13,51 +13,45 @@
13-- See the License for the specific language governing permissions and
13-- See the License for the specific language governing permissions and
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16−{-# LANGUAGE CPP #-}
17
16
18module Network.Minio.PresignedOperations
17module Network.Minio.PresignedOperations
19− ( UrlExpiry,
18+ ( UrlExpiry
20− makePresignedUrl,
19+ , makePresignedUrl
21− presignedPutObjectUrl,
20+ , presignedPutObjectUrl
22− presignedGetObjectUrl,
21+ , presignedGetObjectUrl
23− presignedHeadObjectUrl,
22+ , presignedHeadObjectUrl
23+
24− PostPolicyCondition (..),
24+ , PostPolicyCondition(..)
25− ppCondBucket,
25+ , ppCondBucket
26− ppCondContentLengthRange,
26+ , ppCondContentLengthRange
27− ppCondContentType,
27+ , ppCondContentType
28− ppCondKey,
28+ , ppCondKey
29− ppCondKeyStartsWith,
29+ , ppCondKeyStartsWith
30− ppCondSuccessActionStatus,
30+ , ppCondSuccessActionStatus
31+
31− PostPolicy (..),
32+ , PostPolicy(..)
32− PostPolicyError (..),
33+ , PostPolicyError(..)
33− newPostPolicy,
34+ , newPostPolicy
34− showPostPolicy,
35+ , showPostPolicy
35− presignedPostPolicy,
36+ , presignedPostPolicy
36− )
37−where
37+ ) where
38−
38+
39−import Data.Aeson ((.=))
39+import Data.Aeson ((.=))
40−import qualified Data.Aeson as Json
40+import qualified Data.Aeson as Json
41−import Data.ByteString.Builder (byteString, toLazyByteString)
41+import Data.ByteString.Builder (byteString, toLazyByteString)
42−import qualified Data.HashMap.Strict as H
42+import qualified Data.HashMap.Strict as H
43−import qualified Data.Text as T
43+import qualified Data.Text as T
44−import qualified Data.Time as Time
44+import qualified Data.Time as Time
45+import qualified Network.HTTP.Conduit as NC
46+import qualified Network.HTTP.Types as HT
47+import Network.HTTP.Types.Header (hHost)
48+
45−import Lib.Prelude
49+import Lib.Prelude
46−import qualified Network.HTTP.Client as NClient
50+
47−import qualified Network.HTTP.Types as HT
48−import Network.Minio.API (buildRequest)
49−import Network.Minio.Credentials
51+import Network.Minio.Data
50−import Network.Minio.Data
51−import Network.Minio.Data.Time
52+import Network.Minio.Data.Time
52−import Network.Minio.Errors
53+import Network.Minio.Errors
53−import Network.Minio.Sign.V4
54+import Network.Minio.Sign.V4
54−import Network.URI (uriToString)
55−
56−{- ORMOLU_DISABLE -}
57−#if MIN_VERSION_aeson(2,0,0)
58−import qualified Data.Aeson.Key as A
59−#endif
60−{- ORMOLU_ENABLE -}
61
55
62-- | Generate a presigned URL. This function allows for advanced usage
56-- | Generate a presigned URL. This function allows for advanced usage
63-- - for simple cases prefer the `presigned*Url` functions.
57-- - for simple cases prefer the `presigned*Url` functions.
@@ -67,36 +61,42 @@ import qualified Data.Aeson.Key as A
67--
61--
68-- All extra query parameters or headers are signed, and therefore are
62-- All extra query parameters or headers are signed, and therefore are
69-- required to be sent when the generated URL is actually used.
63-- required to be sent when the generated URL is actually used.
70−makePresignedUrl ::
64+makePresignedUrl :: UrlExpiry -> HT.Method -> Maybe Bucket -> Maybe Object
71− UrlExpiry ->
72− HT.Method ->
73− Maybe Bucket ->
74− Maybe Object ->
75− Maybe Region ->
76− HT.Query ->
77− HT.RequestHeaders ->
65+ -> Maybe Region -> HT.Query -> HT.RequestHeaders
78− Minio ByteString
66+ -> Minio ByteString
79makePresignedUrl expiry method bucket object region extraQuery extraHeaders = do
67makePresignedUrl expiry method bucket object region extraQuery extraHeaders = do
80− when (expiry > 7 * 24 * 3600 || expiry < 0) $
68+ when (expiry > 7*24*3600 || expiry < 0) $
81− throwIO $
82− MErrVInvalidUrlExpiry expiry
69+ throwIO $ MErrVInvalidUrlExpiry expiry
83−
70+
84− let s3ri =
71+ ci <- asks mcConnInfo
85− defaultS3ReqInfo
72+
86− { riPresignExpirySecs = Just expiry,
73+ let
87− riMethod = method,
74+ hostHeader = (hHost, getHostAddr ci)
88− riBucket = bucket,
75+ req = NC.defaultRequest {
89− riObject = object,
76+ NC.method = method
90− riRegion = region,
77+ , NC.secure = connectIsSecure ci
91− riQueryParams = extraQuery,
78+ , NC.host = encodeUtf8 $ connectHost ci
79+ , NC.port = connectPort ci
80+ , NC.path = getS3Path bucket object
92− riHeaders = extraHeaders
81+ , NC.requestHeaders = hostHeader : extraHeaders
82+ , NC.queryString = HT.renderQuery True extraQuery
93− }
83+ }
94−
84+ ts <- liftIO Time.getCurrentTime
95− req <- buildRequest s3ri
85+
96− let uri = NClient.getUri req
86+ let sp = SignParams (connectAccessKey ci) (connectSecretKey ci)
97− uriString = uriToString identity uri ""
87+ ts region (Just expiry) Nothing
98−
88+
99− return $ encodeUtf8 uriString
89+ signPairs = signV4 sp req
90+
91+ qpToAdd = (fmap . fmap) Just signPairs
92+ queryStr = HT.renderQueryBuilder True
93+ ((HT.parseQuery $ NC.queryString req) ++ qpToAdd)
94+ scheme = byteString $ bool "http://" "https://" $ connectIsSecure ci
95+
96+ return $ toS $ toLazyByteString $ scheme
97+ <> byteString (getHostAddr ci)
98+ <> byteString (getS3Path bucket object)
99+ <> queryStr
100
100
101-- | Generate a URL with authentication signature to PUT (upload) an
101-- | Generate a URL with authentication signature to PUT (upload) an
102-- object. Any extra headers if passed, are signed, and so they are
102-- object. Any extra headers if passed, are signed, and so they are
@@ -105,22 +105,12 @@ makePresignedUrl expiry method bucket object region extraQuery extraHeaders = do
105--
105--
106-- For a list of possible headers to pass, please refer to the PUT
106-- For a list of possible headers to pass, please refer to the PUT
107-- object REST API AWS S3 documentation.
107-- object REST API AWS S3 documentation.
108−presignedPutObjectUrl ::
108+presignedPutObjectUrl :: Bucket -> Object -> UrlExpiry -> HT.RequestHeaders
109− Bucket ->
110− Object ->
111− UrlExpiry ->
112− HT.RequestHeaders ->
113− Minio ByteString
109+ -> Minio ByteString
114presignedPutObjectUrl bucket object expirySeconds extraHeaders = do
110presignedPutObjectUrl bucket object expirySeconds extraHeaders = do
115 region <- asks (Just . connectRegion . mcConnInfo)
111 region <- asks (Just . connectRegion . mcConnInfo)
116− makePresignedUrl
112+ makePresignedUrl expirySeconds HT.methodPut
117− expirySeconds
118− HT.methodPut
119− (Just bucket)
120− (Just object)
121− region
122− []
123− extraHeaders
113+ (Just bucket) (Just object) region [] extraHeaders
124
114
125-- | Generate a URL with authentication signature to GET (download) an
115-- | Generate a URL with authentication signature to GET (download) an
126-- object. All extra query parameters and headers passed here will be
116-- object. All extra query parameters and headers passed here will be
@@ -131,23 +121,12 @@ presignedPutObjectUrl bucket object expirySeconds extraHeaders = do
131--
121--
132-- For a list of possible request parameters and headers, please refer
122-- For a list of possible request parameters and headers, please refer
133-- to the GET object REST API AWS S3 documentation.
123-- to the GET object REST API AWS S3 documentation.
134−presignedGetObjectUrl ::
124+presignedGetObjectUrl :: Bucket -> Object -> UrlExpiry -> HT.Query
135− Bucket ->
136− Object ->
137− UrlExpiry ->
138− HT.Query ->
139− HT.RequestHeaders ->
125+ -> HT.RequestHeaders -> Minio ByteString
140− Minio ByteString
141presignedGetObjectUrl bucket object expirySeconds extraQuery extraHeaders = do
126presignedGetObjectUrl bucket object expirySeconds extraQuery extraHeaders = do
142 region <- asks (Just . connectRegion . mcConnInfo)
127 region <- asks (Just . connectRegion . mcConnInfo)
143− makePresignedUrl
128+ makePresignedUrl expirySeconds HT.methodGet
144− expirySeconds
145− HT.methodGet
146− (Just bucket)
147− (Just object)
148− region
149− extraQuery
150− extraHeaders
129+ (Just bucket) (Just object) region extraQuery extraHeaders
151
130
152-- | Generate a URL with authentication signature to make a HEAD
131-- | Generate a URL with authentication signature to make a HEAD
153-- request on an object. This is used to fetch metadata about an
132-- request on an object. This is used to fetch metadata about an
@@ -156,74 +135,50 @@ presignedGetObjectUrl bucket object expirySeconds extraQuery extraHeaders = do
156--
135--
157-- For a list of possible headers to pass, please refer to the HEAD
136-- For a list of possible headers to pass, please refer to the HEAD
158-- object REST API AWS S3 documentation.
137-- object REST API AWS S3 documentation.
159−presignedHeadObjectUrl ::
138+presignedHeadObjectUrl :: Bucket -> Object -> UrlExpiry
160− Bucket ->
161− Object ->
162− UrlExpiry ->
163− HT.RequestHeaders ->
139+ -> HT.RequestHeaders -> Minio ByteString
164− Minio ByteString
165presignedHeadObjectUrl bucket object expirySeconds extraHeaders = do
140presignedHeadObjectUrl bucket object expirySeconds extraHeaders = do
166 region <- asks (Just . connectRegion . mcConnInfo)
141 region <- asks (Just . connectRegion . mcConnInfo)
167− makePresignedUrl
142+ makePresignedUrl expirySeconds HT.methodHead
168− expirySeconds
169− HT.methodHead
170− (Just bucket)
171− (Just object)
172− region
173− []
174− extraHeaders
143+ (Just bucket) (Just object) region [] extraHeaders
175
144
176-- | Represents individual conditions in a Post Policy document.
145-- | Represents individual conditions in a Post Policy document.
177−data PostPolicyCondition
146+data PostPolicyCondition = PPCStartsWith Text Text
178− = PPCStartsWith Text Text
179− | PPCEquals Text Text
147+ | PPCEquals Text Text
180− | PPCRange Text Int64 Int64
148+ | PPCRange Text Int64 Int64
181− deriving stock (Show, Eq)
149+ deriving (Show, Eq)
182
150
183−{- ORMOLU_DISABLE -}
184instance Json.ToJSON PostPolicyCondition where
151instance Json.ToJSON PostPolicyCondition where
185 toJSON (PPCStartsWith k v) = Json.toJSON ["starts-with", k, v]
152 toJSON (PPCStartsWith k v) = Json.toJSON ["starts-with", k, v]
186−#if MIN_VERSION_aeson(2,0,0)
187− toJSON (PPCEquals k v) = Json.object [(A.fromText k) .= v]
188−#else
189 toJSON (PPCEquals k v) = Json.object [k .= v]
153 toJSON (PPCEquals k v) = Json.object [k .= v]
190−#endif
191 toJSON (PPCRange k minVal maxVal) =
154 toJSON (PPCRange k minVal maxVal) =
192 Json.toJSON [Json.toJSON k, Json.toJSON minVal, Json.toJSON maxVal]
155 Json.toJSON [Json.toJSON k, Json.toJSON minVal, Json.toJSON maxVal]
193
156
194 toEncoding (PPCStartsWith k v) = Json.foldable ["starts-with", k, v]
157 toEncoding (PPCStartsWith k v) = Json.foldable ["starts-with", k, v]
195−#if MIN_VERSION_aeson(2,0,0)
196− toEncoding (PPCEquals k v) = Json.pairs ((A.fromText k) .= v)
197−#else
198 toEncoding (PPCEquals k v) = Json.pairs (k .= v)
158 toEncoding (PPCEquals k v) = Json.pairs (k .= v)
199−#endif
200 toEncoding (PPCRange k minVal maxVal) =
159 toEncoding (PPCRange k minVal maxVal) =
201 Json.foldable [Json.toJSON k, Json.toJSON minVal, Json.toJSON maxVal]
160 Json.foldable [Json.toJSON k, Json.toJSON minVal, Json.toJSON maxVal]
202−{- ORMOLU_ENABLE -}
203
161
204-- | A PostPolicy is required to perform uploads via browser forms.
162-- | A PostPolicy is required to perform uploads via browser forms.
205−data PostPolicy = PostPolicy
163+data PostPolicy = PostPolicy {
206− { expiration :: UTCTime,
164+ expiration :: UTCTime
207− conditions :: [PostPolicyCondition]
165+ , conditions :: [PostPolicyCondition]
208− }
209− deriving stock (Show, Eq)
166+ } deriving (Show, Eq)
210
167
211instance Json.ToJSON PostPolicy where
168instance Json.ToJSON PostPolicy where
212 toJSON (PostPolicy e c) =
169 toJSON (PostPolicy e c) =
213− Json.object
214− [ "expiration" .= iso8601TimeFormat e,
170+ Json.object $ [ "expiration" .= iso8601TimeFormat e
215− "conditions" .= c
171+ , "conditions" .= c
216− ]
172+ ]
217 toEncoding (PostPolicy e c) =
173 toEncoding (PostPolicy e c) =
218 Json.pairs ("expiration" .= iso8601TimeFormat e <> "conditions" .= c)
174 Json.pairs ("expiration" .= iso8601TimeFormat e <> "conditions" .= c)
219
175
220-- | Possible validation errors when creating a PostPolicy.
176-- | Possible validation errors when creating a PostPolicy.
221−data PostPolicyError
177+data PostPolicyError = PPEKeyNotSpecified
222− = PPEKeyNotSpecified
223− | PPEBucketNotSpecified
178+ | PPEBucketNotSpecified
224− | PPEConditionKeyEmpty
179+ | PPEConditionKeyEmpty
225− | PPERangeInvalid
180+ | PPERangeInvalid
226− deriving stock (Show, Eq)
181+ deriving (Eq, Show)
227
182
228-- | Set the bucket name that the upload should use.
183-- | Set the bucket name that the upload should use.
229ppCondBucket :: Bucket -> PostPolicyCondition
184ppCondBucket :: Bucket -> PostPolicyCondition
@@ -231,10 +186,8 @@ ppCondBucket = PPCEquals "bucket"
231
186
232-- | Set the content length range constraint with minimum and maximum
187-- | Set the content length range constraint with minimum and maximum
233-- byte count values.
188-- byte count values.
234−ppCondContentLengthRange ::
189+ppCondContentLengthRange :: Int64 -> Int64
235− Int64 ->
236− Int64 ->
237− PostPolicyCondition
190+ -> PostPolicyCondition
238ppCondContentLengthRange = PPCRange "content-length-range"
191ppCondContentLengthRange = PPCRange "content-length-range"
239
192
240-- | Set the content-type header for the upload.
193-- | Set the content-type header for the upload.
@@ -257,99 +210,83 @@ ppCondSuccessActionStatus n =
257
210
258-- | This function creates a PostPolicy after validating its
211-- | This function creates a PostPolicy after validating its
259-- arguments.
212-- arguments.
260−newPostPolicy ::
261− UTCTime ->
262− [PostPolicyCondition] ->
213+newPostPolicy :: UTCTime -> [PostPolicyCondition]
263− Either PostPolicyError PostPolicy
214+ -> Either PostPolicyError PostPolicy
264newPostPolicy expirationTime conds
215newPostPolicy expirationTime conds
265 -- object name condition must be present
216 -- object name condition must be present
266 | not $ any (keyEquals "key") conds =
217 | not $ any (keyEquals "key") conds =
267 Left PPEKeyNotSpecified
218 Left PPEKeyNotSpecified
219+
268 -- bucket name condition must be present
220 -- bucket name condition must be present
269 | not $ any (keyEquals "bucket") conds =
221 | not $ any (keyEquals "bucket") conds =
270 Left PPEBucketNotSpecified
222 Left PPEBucketNotSpecified
223+
271 -- a condition with an empty key is invalid
224 -- a condition with an empty key is invalid
272 | any (keyEquals "") conds || any isEmptyRangeKey conds =
225 | any (keyEquals "") conds || any isEmptyRangeKey conds =
273 Left PPEConditionKeyEmpty
226 Left PPEConditionKeyEmpty
227+
274 -- invalid range check
228 -- invalid range check
275 | any isInvalidRange conds =
229 | any isInvalidRange conds =
276 Left PPERangeInvalid
230 Left PPERangeInvalid
231+
277 -- all good!
232 -- all good!
278 | otherwise =
233 | otherwise =
279 return $ PostPolicy expirationTime conds
234 return $ PostPolicy expirationTime conds
235+
280 where
236 where
281 keyEquals k' (PPCStartsWith k _) = k == k'
237 keyEquals k' (PPCStartsWith k _) = k == k'
282− keyEquals k' (PPCEquals k _) = k == k'
238+ keyEquals k' (PPCEquals k _) = k == k'
283− keyEquals _ _ = False
239+ keyEquals _ _ = False
240+
284 isEmptyRangeKey (PPCRange k _ _) = k == ""
241 isEmptyRangeKey (PPCRange k _ _) = k == ""
285− isEmptyRangeKey _ = False
242+ isEmptyRangeKey _ = False
243+
286 isInvalidRange (PPCRange _ mi ma) = mi < 0 || mi > ma
244 isInvalidRange (PPCRange _ mi ma) = mi < 0 || mi > ma
287− isInvalidRange _ = False
245+ isInvalidRange _ = False
288
246
289-- | Convert Post Policy to a string (e.g. for printing).
247-- | Convert Post Policy to a string (e.g. for printing).
290showPostPolicy :: PostPolicy -> ByteString
248showPostPolicy :: PostPolicy -> ByteString
291−showPostPolicy = toStrictBS . Json.encode
249+showPostPolicy = toS . Json.encode
292
250
293-- | Generate a presigned URL and POST policy to upload files via a
251-- | Generate a presigned URL and POST policy to upload files via a
294-- browser. On success, this function returns a URL and POST
252-- browser. On success, this function returns a URL and POST
295-- form-data.
253-- form-data.
296−presignedPostPolicy ::
254+presignedPostPolicy :: PostPolicy
297− PostPolicy ->
298− Minio (ByteString, H.HashMap Text ByteString)
255+ -> Minio (ByteString, H.HashMap Text ByteString)
299presignedPostPolicy p = do
256presignedPostPolicy p = do
300 ci <- asks mcConnInfo
257 ci <- asks mcConnInfo
301− signTime <- liftIO Time.getCurrentTime
258+ signTime <- liftIO $ Time.getCurrentTime
302− mgr <- asks mcConnManager
259+
303− cv <- liftIO $ getCredential (connectCreds ci) (getEndpoint ci) mgr
260+ let
304−
305− let extraConditions signParams =
261+ extraConditions =
306− [ PPCEquals "x-amz-date" (toText $ awsTimeFormat signTime),
262+ [ PPCEquals "x-amz-date" (toS $ awsTimeFormat signTime)
307− PPCEquals "x-amz-algorithm" "AWS4-HMAC-SHA256",
263+ , PPCEquals "x-amz-algorithm" "AWS4-HMAC-SHA256"
308− PPCEquals
309− "x-amz-credential"
264+ , PPCEquals "x-amz-credential"
310− ( T.intercalate
265+ (T.intercalate "/" [connectAccessKey ci,
311− "/"
266+ decodeUtf8 $ mkScope signTime region])
312− [ coerce $ cvAccessKey cv,
313− decodeUtf8 $ credentialScope signParams
314− ]
315− )
316− ]
267+ ]
317− ppWithCreds signParams =
268+ ppWithCreds = p {
318− p
319− { conditions = conditions p ++ extraConditions signParams
269+ conditions = conditions p ++ extraConditions
320− }
270+ }
321− sp =
271+ sp = SignParams (connectAccessKey ci) (connectSecretKey ci)
322− SignParams
323− (coerce $ cvAccessKey cv)
324− (coerce $ cvSecretKey cv)
325− (coerce $ cvSessionToken cv)
326− ServiceS3
327− signTime
328− (Just $ connectRegion ci)
272+ signTime (Just $ connectRegion ci) Nothing Nothing
329− Nothing
330− Nothing
331− signData = signV4PostPolicy (showPostPolicy $ ppWithCreds sp) sp
273+ signData = signV4PostPolicy (showPostPolicy ppWithCreds) sp
274+
275+
332− -- compute form-data
276+ -- compute form-data
333− mkPair (PPCStartsWith k v) = Just (k, v)
277+ mkPair (PPCStartsWith k v) = Just (k, v)
334− mkPair (PPCEquals k v) = Just (k, v)
278+ mkPair (PPCEquals k v) = Just (k, v)
335− mkPair _ = Nothing
279+ mkPair _ = Nothing
336− formFromPolicy =
280+ formFromPolicy = H.map toS $ H.fromList $ catMaybes $
337− H.map encodeUtf8 $
281+ mkPair <$> conditions ppWithCreds
338− H.fromList $
339− mapMaybe
340− mkPair
341− (conditions $ ppWithCreds sp)
342− formData = formFromPolicy `H.union` signData
282+ formData = formFromPolicy `H.union` signData
283+
343− -- compute POST upload URL
284+ -- compute POST upload URL
344− bucket = H.lookupDefault "" "bucket" formData
285+ bucket = H.lookupDefault "" "bucket" formData
345− scheme = byteString $ bool "http://" "https://" $ connectIsSecure ci
286+ scheme = byteString $ bool "http://" "https://" $ connectIsSecure ci
346− url =
287+ region = connectRegion ci
347− toStrictBS $
288+
348− toLazyByteString $
349− scheme
350− <> byteString (getHostAddr ci)
289+ url = toS $ toLazyByteString $ scheme <> byteString (getHostAddr ci) <>
351− <> byteString "/"
352− <> byteString bucket
290+ byteString "/" <> byteString (toS bucket) <> byteString "/"
353− <> byteString "/"
354
291
355 return (url, formData)
292 return (url, formData)
src/Network/Minio/PutObject.hs+69−85
@@ -15,24 +15,29 @@
15--
15--
16
16
17module Network.Minio.PutObject
17module Network.Minio.PutObject
18+ (
18− ( putObjectInternal,
19+ putObjectInternal
19− ObjectData (..),
20+ , ObjectData(..)
20− selectPartSizes,
21+ , selectPartSizes
21− )
22−where
22+ ) where
23−
23+
24+
24−import Conduit (takeC)
25+import Conduit (takeC)
25−import qualified Conduit as C
26+import qualified Conduit as C
26−import qualified Data.ByteString.Lazy as LBS
27+import qualified Data.ByteString.Lazy as LBS
27−import qualified Data.Conduit.Binary as CB
28+import qualified Data.Conduit.Binary as CB
28import qualified Data.Conduit.Combinators as CC
29import qualified Data.Conduit.Combinators as CC
29−import qualified Data.Conduit.List as CL
30+import qualified Data.Conduit.List as CL
30−import qualified Data.List as List
31+import qualified Data.List as List
32+
33+
31−import Lib.Prelude
34+import Lib.Prelude
35+
32−import Network.Minio.Data
36+import Network.Minio.Data
33−import Network.Minio.Errors
37+import Network.Minio.Errors
34−import Network.Minio.S3API
38+import Network.Minio.S3API
35−import Network.Minio.Utils
39+import Network.Minio.Utils
40+
36
41
37-- | A data-type to represent the source data for an object. A
42-- | A data-type to represent the source data for an object. A
38-- file-path or a producer-conduit may be provided.
43-- file-path or a producer-conduit may be provided.
@@ -45,45 +50,37 @@ import Network.Minio.Utils
45-- the input - if it is not provided, upload will continue until the
50-- the input - if it is not provided, upload will continue until the
46-- stream ends or the object reaches `maxObjectSize` size.
51-- stream ends or the object reaches `maxObjectSize` size.
47data ObjectData m
52data ObjectData m
48− = -- | Takes filepath and optional
53+ = ODFile FilePath (Maybe Int64) -- ^ Takes filepath and optional
49− -- size.
54+ -- size.
50− ODFile FilePath (Maybe Int64)
51− | -- | Pass
52− -- size
53− -- (bytes)
54− -- if
55− -- known.
56− ODStream (C.ConduitM () ByteString m ()) (Maybe Int64)
55+ | ODStream (C.ConduitM () ByteString m ()) (Maybe Int64) -- ^ Pass
56+ -- size
57+ -- (bytes)
58+ -- if
59+ -- known.
57
60
58-- | Put an object from ObjectData. This high-level API handles
61-- | Put an object from ObjectData. This high-level API handles
59-- objects of all sizes, and even if the object size is unknown.
62-- objects of all sizes, and even if the object size is unknown.
60−putObjectInternal ::
63+putObjectInternal :: Bucket -> Object -> PutObjectOptions
61− Bucket ->
62− Object ->
63− PutObjectOptions ->
64− ObjectData Minio ->
64+ -> ObjectData Minio -> Minio ETag
65− Minio ETag
66putObjectInternal b o opts (ODStream src sizeMay) = do
65putObjectInternal b o opts (ODStream src sizeMay) = do
67 case sizeMay of
66 case sizeMay of
68 -- unable to get size, so assume non-seekable file
67 -- unable to get size, so assume non-seekable file
69 Nothing -> sequentialMultipartUpload b o opts Nothing src
68 Nothing -> sequentialMultipartUpload b o opts Nothing src
69+
70 -- got file size, so check for single/multipart upload
70 -- got file size, so check for single/multipart upload
71 Just size ->
71 Just size ->
72− if
73− | size <= 64 * oneMiB -> do
72+ if | size <= 64 * oneMiB -> do
74− bs <- C.runConduit $ src C..| takeC (fromIntegral size) C..| CB.sinkLbs
73+ bs <- C.runConduit $ src C..| takeC (fromIntegral size) C..| CB.sinkLbs
75− putObjectSingle' b o (pooToHeaders opts) $ LBS.toStrict bs
74+ putObjectSingle' b o (pooToHeaders opts) $ LBS.toStrict bs
76− | size > maxObjectSize -> throwIO $ MErrVPutSizeExceeded size
75+ | size > maxObjectSize -> throwIO $ MErrVPutSizeExceeded size
77− | otherwise -> sequentialMultipartUpload b o opts (Just size) src
76+ | otherwise -> sequentialMultipartUpload b o opts (Just size) src
77+
78putObjectInternal b o opts (ODFile fp sizeMay) = do
78putObjectInternal b o opts (ODFile fp sizeMay) = do
79 hResE <- withNewHandle fp $ \h ->
79 hResE <- withNewHandle fp $ \h ->
80− liftA2 (,) (isHandleSeekable h) (getFileSize h)
80+ liftM2 (,) (isHandleSeekable h) (getFileSize h)
81
81
82− (isSeekable, handleSizeMay) <-
82+ (isSeekable, handleSizeMay) <- either (const $ return (False, Nothing)) return
83− either
84− (const $ return (False, Nothing))
85− return
86− hResE
83+ hResE
87
84
88 -- prefer given size to queried size.
85 -- prefer given size to queried size.
89 let finalSizeMay = listToMaybe $ catMaybes [sizeMay, handleSizeMay]
86 let finalSizeMay = listToMaybe $ catMaybes [sizeMay, handleSizeMay]
@@ -91,25 +88,18 @@ putObjectInternal b o opts (ODFile fp sizeMay) = do
91 case finalSizeMay of
88 case finalSizeMay of
92 -- unable to get size, so assume non-seekable file
89 -- unable to get size, so assume non-seekable file
93 Nothing -> sequentialMultipartUpload b o opts Nothing $ CB.sourceFile fp
90 Nothing -> sequentialMultipartUpload b o opts Nothing $ CB.sourceFile fp
91+
94 -- got file size, so check for single/multipart upload
92 -- got file size, so check for single/multipart upload
95 Just size ->
93 Just size ->
96− if
97− | size <= 64 * oneMiB ->
98− either throwIO return
94+ if | size <= 64 * oneMiB -> either throwIO return =<<
99− =<< withNewHandle fp (\h -> putObjectSingle b o (pooToHeaders opts) h 0 size)
95+ withNewHandle fp (\h -> putObjectSingle b o (pooToHeaders opts) h 0 size)
100− | size > maxObjectSize -> throwIO $ MErrVPutSizeExceeded size
96+ | size > maxObjectSize -> throwIO $ MErrVPutSizeExceeded size
101− | isSeekable -> parallelMultipartUpload b o opts fp size
97+ | isSeekable -> parallelMultipartUpload b o opts fp size
102− | otherwise ->
103− sequentialMultipartUpload b o opts (Just size) $
98+ | otherwise -> sequentialMultipartUpload b o opts (Just size) $
104− CB.sourceFile fp
99+ CB.sourceFile fp
105−
100+
106−parallelMultipartUpload ::
101+parallelMultipartUpload :: Bucket -> Object -> PutObjectOptions
107− Bucket ->
108− Object ->
109− PutObjectOptions ->
110− FilePath ->
111− Int64 ->
112− Minio ETag
102+ -> FilePath -> Int64 -> Minio ETag
113parallelMultipartUpload b o opts filePath size = do
103parallelMultipartUpload b o opts filePath size = do
114 -- get a new upload id.
104 -- get a new upload id.
115 uploadId <- newMultipartUpload b o (pooToHeaders opts)
105 uploadId <- newMultipartUpload b o (pooToHeaders opts)
@@ -119,17 +109,15 @@ parallelMultipartUpload b o opts filePath size = do
119 let threads = fromMaybe 10 $ pooNumThreads opts
109 let threads = fromMaybe 10 $ pooNumThreads opts
120
110
121 -- perform upload with 'threads' threads
111 -- perform upload with 'threads' threads
122− uploadedPartsE <-
123− limitedMapConcurrently
112+ uploadedPartsE <- limitedMapConcurrently (fromIntegral threads)
124− (fromIntegral threads)
125− (uploadPart uploadId)
113+ (uploadPart uploadId) partSizeInfo
126− partSizeInfo
127
114
128 -- if there were any errors, rethrow exception.
115 -- if there were any errors, rethrow exception.
129 mapM_ throwIO $ lefts uploadedPartsE
116 mapM_ throwIO $ lefts uploadedPartsE
130
117
131 -- if we get here, all parts were successfully uploaded.
118 -- if we get here, all parts were successfully uploaded.
132 completeMultipartUpload b o uploadId $ rights uploadedPartsE
119 completeMultipartUpload b o uploadId $ rights uploadedPartsE
120+
133 where
121 where
134 uploadPart uploadId (partNum, offset, sz) =
122 uploadPart uploadId (partNum, offset, sz) =
135 withNewHandle filePath $ \h -> do
123 withNewHandle filePath $ \h -> do
@@ -137,13 +125,10 @@ parallelMultipartUpload b o opts filePath size = do
137 putObjectPart b o uploadId partNum [] payload
125 putObjectPart b o uploadId partNum [] payload
138
126
139-- | Upload multipart object from conduit source sequentially
127-- | Upload multipart object from conduit source sequentially
140−sequentialMultipartUpload ::
128+sequentialMultipartUpload :: Bucket -> Object -> PutObjectOptions
141− Bucket ->
142− Object ->
143− PutObjectOptions ->
144− Maybe Int64 ->
129+ -> Maybe Int64
145− C.ConduitM () ByteString Minio () ->
130+ -> C.ConduitM () ByteString Minio ()
146− Minio ETag
131+ -> Minio ETag
147sequentialMultipartUpload b o opts sizeMay src = do
132sequentialMultipartUpload b o opts sizeMay src = do
148 -- get a new upload id.
133 -- get a new upload id.
149 uploadId <- newMultipartUpload b o (pooToHeaders opts)
134 uploadId <- newMultipartUpload b o (pooToHeaders opts)
@@ -151,23 +136,22 @@ sequentialMultipartUpload b o opts sizeMay src = do
151 -- upload parts in loop
136 -- upload parts in loop
152 let partSizes = selectPartSizes $ maybe maxObjectSize identity sizeMay
137 let partSizes = selectPartSizes $ maybe maxObjectSize identity sizeMay
153 (pnums, _, sizes) = List.unzip3 partSizes
138 (pnums, _, sizes) = List.unzip3 partSizes
154− uploadedParts <-
139+ uploadedParts <- C.runConduit
155− C.runConduit $
156− src
140+ $ src
157− C..| chunkBSConduit (map fromIntegral sizes)
141+ C..| chunkBSConduit (map fromIntegral sizes)
158− C..| CL.map PayloadBS
142+ C..| CL.map PayloadBS
159− C..| uploadPart' uploadId pnums
143+ C..| uploadPart' uploadId pnums
160− C..| CC.sinkList
144+ C..| CC.sinkList
161
145
162 -- complete multipart upload
146 -- complete multipart upload
163 completeMultipartUpload b o uploadId uploadedParts
147 completeMultipartUpload b o uploadId uploadedParts
148+
164 where
149 where
165 uploadPart' _ [] = return ()
150 uploadPart' _ [] = return ()
166− uploadPart' uid (pn : pns) = do
151+ uploadPart' uid (pn:pns) = do
167 payloadMay <- C.await
152 payloadMay <- C.await
168 case payloadMay of
153 case payloadMay of
169 Nothing -> return ()
154 Nothing -> return ()
170− Just payload -> do
171− pinfo <- lift $ putObjectPart b o uid pn [] payload
155+ Just payload -> do pinfo <- lift $ putObjectPart b o uid pn [] payload
172− C.yield pinfo
156+ C.yield pinfo
173− uploadPart' uid pns
157+ uploadPart' uid pns
src/Network/Minio/S3API.hs+319−481
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017, 2018 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -14,185 +14,150 @@
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16
16
17−-- |
18−-- Module: Network.Minio.S3API
19−-- Copyright: (c) 2017-2023 MinIO Dev Team
20−-- License: Apache 2.0
21−-- Maintainer: MinIO Dev Team <dev@min.io>
22−--
23−-- Lower-level API for S3 compatible object stores. Start with @Network.Minio@
24−-- and use this only if needed.
25module Network.Minio.S3API
17module Network.Minio.S3API
18+ (
26− ( Region,
19+ Region
27− getLocation,
20+ , getLocation
28−
21+
29− -- * Listing buckets
22+ -- * Listing buckets
30−
31− --------------------
23+ --------------------
32− getService,
24+ , getService
33−
25+
34− -- * Listing objects
26+ -- * Listing objects
35−
36− --------------------
27+ --------------------
37− ListObjectsResult (..),
28+ , ListObjectsResult(..)
38− ListObjectsV1Result (..),
29+ , ListObjectsV1Result(..)
39− listObjects',
30+ , listObjects'
40− listObjectsV1',
31+ , listObjectsV1'
41−
32+
42− -- * Retrieving buckets
33+ -- * Retrieving buckets
43− headBucket,
34+ , headBucket
44−
35+
45− -- * Retrieving objects
36+ -- * Retrieving objects
46−
47− -----------------------
37+ -----------------------
48− getObject',
38+ , getObject'
49− headObject,
39+ , headObject
50−
40+
51− -- * Creating buckets and objects
41+ -- * Creating buckets and objects
52−
53− ---------------------------------
42+ ---------------------------------
54− putBucket,
43+ , putBucket
55− ETag,
44+ , ETag
56− maxSinglePutObjectSizeBytes,
45+ , maxSinglePutObjectSizeBytes
57− putObjectSingle',
46+ , putObjectSingle'
58− putObjectSingle,
47+ , putObjectSingle
59− copyObjectSingle,
48+ , copyObjectSingle
60−
49+
61− -- * Multipart Upload APIs
50+ -- * Multipart Upload APIs
62−
63− --------------------------
51+ --------------------------
64− UploadId,
52+ , UploadId
65− PartTuple,
53+ , PartTuple
66− Payload (..),
54+ , Payload(..)
67− PartNumber,
55+ , PartNumber
68− newMultipartUpload,
56+ , newMultipartUpload
69− putObjectPart,
57+ , putObjectPart
70− copyObjectPart,
58+ , copyObjectPart
71− completeMultipartUpload,
59+ , completeMultipartUpload
72− abortMultipartUpload,
60+ , abortMultipartUpload
73− ListUploadsResult (..),
61+ , ListUploadsResult(..)
74− listIncompleteUploads',
62+ , listIncompleteUploads'
75− ListPartsResult (..),
63+ , ListPartsResult(..)
76− listIncompleteParts',
64+ , listIncompleteParts'
77−
65+
78− -- * Deletion APIs
66+ -- * Deletion APIs
79−
80− --------------------------
67+ --------------------------
81− deleteBucket,
68+ , deleteBucket
82− deleteObject,
69+ , deleteObject
83−
70+
84− -- * Presigned Operations
71+ -- * Presigned Operations
85−
86− -----------------------------
72+ -----------------------------
87− module Network.Minio.PresignedOperations,
73+ , module Network.Minio.PresignedOperations
88−
74+
89− -- ** Bucket Policies
75+ -- ** Bucket Policies
90− getBucketPolicy,
76+ , getBucketPolicy
91− setBucketPolicy,
77+ , setBucketPolicy
92−
78+
93− -- * Bucket Notifications
79+ -- * Bucket Notifications
94−
95− -------------------------
80+ -------------------------
96− Notification (..),
81+ , Notification(..)
97− NotificationConfig (..),
82+ , NotificationConfig(..)
98− Arn,
83+ , Arn
99− Event (..),
84+ , Event(..)
100− Filter (..),
85+ , Filter(..)
101− FilterKey (..),
86+ , FilterKey(..)
102− FilterRules (..),
87+ , FilterRules(..)
103− FilterRule (..),
88+ , FilterRule(..)
104− getBucketNotification,
89+ , getBucketNotification
105− putBucketNotification,
90+ , putBucketNotification
106− removeAllBucketNotification,
91+ , removeAllBucketNotification
107− )
108−where
92+ ) where
109−
93+
110−import qualified Data.ByteString as BS
94+import qualified Data.ByteString as BS
111−import qualified Data.Text as T
95+import qualified Data.Text as T
112−import Lib.Prelude
113−import qualified Network.HTTP.Conduit as NC
96+import qualified Network.HTTP.Conduit as NC
114−import qualified Network.HTTP.Types as HT
97+import qualified Network.HTTP.Types as HT
115−import Network.HTTP.Types.Status (status404)
98+import Network.HTTP.Types.Status (status404)
99+import UnliftIO (Handler (Handler))
100+
101+import Lib.Prelude
102+
116−import Network.Minio.API
103+import Network.Minio.API
117−import Network.Minio.APICommon
104+import Network.Minio.APICommon
118−import Network.Minio.Data
105+import Network.Minio.Data
119−import Network.Minio.Errors
106+import Network.Minio.Errors
120−import Network.Minio.PresignedOperations
107+import Network.Minio.PresignedOperations
121−import Network.Minio.Utils
108+import Network.Minio.Utils
122−import Network.Minio.XmlGenerator
109+import Network.Minio.XmlGenerator
123−import Network.Minio.XmlParser
110+import Network.Minio.XmlParser
124−import UnliftIO (Handler (Handler))
125
111
126-- | Fetch all buckets from the service.
112-- | Fetch all buckets from the service.
127getService :: Minio [BucketInfo]
113getService :: Minio [BucketInfo]
128getService = do
114getService = do
129− resp <-
130− executeRequest $
131− defaultS3ReqInfo
115+ resp <- executeRequest $ defaultS3ReqInfo {
132− { riNeedsLocation = False
116+ riNeedsLocation = False
133− }
117+ }
134 parseListBuckets $ NC.responseBody resp
118 parseListBuckets $ NC.responseBody resp
135
119
136-- Parse headers from getObject and headObject calls.
120-- Parse headers from getObject and headObject calls.
137parseGetObjectHeaders :: Object -> [HT.Header] -> Maybe ObjectInfo
121parseGetObjectHeaders :: Object -> [HT.Header] -> Maybe ObjectInfo
138parseGetObjectHeaders object headers =
122parseGetObjectHeaders object headers =
139− let metadataPairs = getMetadata headers
123+ let metadataPairs = getMetadata headers
140− userMetadata = getUserMetadataMap metadataPairs
124+ userMetadata = getUserMetadataMap metadataPairs
141− metadata = getNonUserMetadataMap metadataPairs
125+ metadata = getNonUserMetadataMap metadataPairs
142− in ObjectInfo
126+ in ObjectInfo <$> Just object
143− <$> Just object
144− <*> getLastModifiedHeader headers
127+ <*> getLastModifiedHeader headers
145− <*> getETagHeader headers
128+ <*> getETagHeader headers
146− <*> getContentLength headers
129+ <*> getContentLength headers
147− <*> Just userMetadata
130+ <*> Just userMetadata
148− <*> Just metadata
131+ <*> Just metadata
149
132
150-- | GET an object from the service and return parsed ObjectInfo and a
133-- | GET an object from the service and return parsed ObjectInfo and a
151-- conduit source for the object content
134-- conduit source for the object content
152−getObject' ::
135+getObject' :: Bucket -> Object -> HT.Query -> [HT.Header]
153− Bucket ->
154− Object ->
155− HT.Query ->
156− [HT.Header] ->
157− Minio GetObjectResponse
136+ -> Minio GetObjectResponse
158getObject' bucket object queryParams headers = do
137getObject' bucket object queryParams headers = do
159− resp <- mkStreamRequest reqInfo
138+ resp <- mkStreamRequest reqInfo
160− let objInfoMaybe = parseGetObjectHeaders object $ NC.responseHeaders resp
139+ let objInfoMaybe = parseGetObjectHeaders object $ NC.responseHeaders resp
161− objInfo <-
162− maybe
163− (throwIO MErrVInvalidObjectInfoResponse)
140+ objInfo <- maybe (throwIO MErrVInvalidObjectInfoResponse) return
164− return
165− objInfoMaybe
141+ objInfoMaybe
166− return $
167− GetObjectResponse
168− { gorObjectInfo = objInfo,
142+ return $ GetObjectResponse { gorObjectInfo = objInfo
169− gorObjectStream = NC.responseBody resp
143+ , gorObjectStream = NC.responseBody resp
170− }
144+ }
171 where
145 where
172− reqInfo =
173− defaultS3ReqInfo
174− { riBucket = Just bucket,
146+ reqInfo = defaultS3ReqInfo { riBucket = Just bucket
175− riObject = Just object,
147+ , riObject = Just object
176− riQueryParams = queryParams,
148+ , riQueryParams = queryParams
177− riHeaders =
149+ , riHeaders = headers
178− headers
179− -- This header is required for safety as otherwise http-client,
180− -- sends Accept-Encoding: gzip, and the server may actually gzip
181− -- body. In that case Content-Length header will be missing.
182− <> [("Accept-Encoding", "identity")]
183− }
150+ }
184
151
185-- | Creates a bucket via a PUT bucket call.
152-- | Creates a bucket via a PUT bucket call.
186putBucket :: Bucket -> Region -> Minio ()
153putBucket :: Bucket -> Region -> Minio ()
187putBucket bucket location = do
154putBucket bucket location = do
188 ns <- asks getSvcNamespace
155 ns <- asks getSvcNamespace
189− void $
156+ void $ executeRequest $
190− executeRequest $
191− defaultS3ReqInfo
192− { riMethod = HT.methodPut,
157+ defaultS3ReqInfo { riMethod = HT.methodPut
193− riBucket = Just bucket,
158+ , riBucket = Just bucket
194− riPayload = PayloadBS $ mkCreateBucketConfig ns location,
159+ , riPayload = PayloadBS $ mkCreateBucketConfig ns location
195− riNeedsLocation = False
160+ , riNeedsLocation = False
196 }
161 }
197
162
198-- | Single PUT object size.
163-- | Single PUT object size.
@@ -208,432 +173,314 @@ putObjectSingle' bucket object headers bs = do
208 let size = fromIntegral (BS.length bs)
173 let size = fromIntegral (BS.length bs)
209 -- check length is within single PUT object size.
174 -- check length is within single PUT object size.
210 when (size > maxSinglePutObjectSizeBytes) $
175 when (size > maxSinglePutObjectSizeBytes) $
211− throwIO $
212− MErrVSinglePUTSizeExceeded size
176+ throwIO $ MErrVSinglePUTSizeExceeded size
213
177
214 let payload = mkStreamingPayload $ PayloadBS bs
178 let payload = mkStreamingPayload $ PayloadBS bs
215− resp <-
216− executeRequest $
179+ resp <- executeRequest $
217− defaultS3ReqInfo
218− { riMethod = HT.methodPut,
180+ defaultS3ReqInfo { riMethod = HT.methodPut
219− riBucket = Just bucket,
181+ , riBucket = Just bucket
220− riObject = Just object,
182+ , riObject = Just object
221− riHeaders = headers,
183+ , riHeaders = headers
222− riPayload = payload
184+ , riPayload = payload
223− }
185+ }
224
186
225 let rheaders = NC.responseHeaders resp
187 let rheaders = NC.responseHeaders resp
226 etag = getETagHeader rheaders
188 etag = getETagHeader rheaders
227 maybe
189 maybe
228 (throwIO MErrVETagHeaderNotFound)
190 (throwIO MErrVETagHeaderNotFound)
229− return
191+ return etag
230− etag
231
192
232-- | PUT an object into the service. This function performs a single
193-- | PUT an object into the service. This function performs a single
233-- PUT object call, and so can only transfer objects upto 5GiB.
194-- PUT object call, and so can only transfer objects upto 5GiB.
234−putObjectSingle ::
195+putObjectSingle :: Bucket -> Object -> [HT.Header] -> Handle -> Int64
235− Bucket ->
236− Object ->
237− [HT.Header] ->
238− Handle ->
239− Int64 ->
240− Int64 ->
241− Minio ETag
196+ -> Int64 -> Minio ETag
242putObjectSingle bucket object headers h offset size = do
197putObjectSingle bucket object headers h offset size = do
243 -- check length is within single PUT object size.
198 -- check length is within single PUT object size.
244 when (size > maxSinglePutObjectSizeBytes) $
199 when (size > maxSinglePutObjectSizeBytes) $
245− throwIO $
246− MErrVSinglePUTSizeExceeded size
200+ throwIO $ MErrVSinglePUTSizeExceeded size
247
201
248 -- content-length header is automatically set by library.
202 -- content-length header is automatically set by library.
249 let payload = mkStreamingPayload $ PayloadH h offset size
203 let payload = mkStreamingPayload $ PayloadH h offset size
250− resp <-
251− executeRequest $
204+ resp <- executeRequest $
252− defaultS3ReqInfo
253− { riMethod = HT.methodPut,
205+ defaultS3ReqInfo { riMethod = HT.methodPut
254− riBucket = Just bucket,
206+ , riBucket = Just bucket
255− riObject = Just object,
207+ , riObject = Just object
256− riHeaders = headers,
208+ , riHeaders = headers
257− riPayload = payload
209+ , riPayload = payload
258− }
210+ }
259
211
260 let rheaders = NC.responseHeaders resp
212 let rheaders = NC.responseHeaders resp
261 etag = getETagHeader rheaders
213 etag = getETagHeader rheaders
262 maybe
214 maybe
263 (throwIO MErrVETagHeaderNotFound)
215 (throwIO MErrVETagHeaderNotFound)
264− return
216+ return etag
265− etag
266
217
267-- | List objects in a bucket matching prefix up to delimiter,
218-- | List objects in a bucket matching prefix up to delimiter,
268-- starting from nextMarker.
219-- starting from nextMarker.
269−listObjectsV1' ::
220+listObjectsV1' :: Bucket -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Int
270− Bucket ->
271− Maybe Text ->
272− Maybe Text ->
273− Maybe Text ->
274− Maybe Int ->
275− Minio ListObjectsV1Result
221+ -> Minio ListObjectsV1Result
276listObjectsV1' bucket prefix nextMarker delimiter maxKeys = do
222listObjectsV1' bucket prefix nextMarker delimiter maxKeys = do
277− resp <-
278− executeRequest $
279− defaultS3ReqInfo
280− { riMethod = HT.methodGet,
223+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodGet
281− riBucket = Just bucket,
224+ , riBucket = Just bucket
282− riQueryParams = mkOptionalParams params
225+ , riQueryParams = mkOptionalParams params
283− }
226+ }
284 parseListObjectsV1Response $ NC.responseBody resp
227 parseListObjectsV1Response $ NC.responseBody resp
285 where
228 where
286− params =
229+ params = [
287− [ ("marker", nextMarker),
230+ ("marker", nextMarker)
288− ("prefix", prefix),
231+ , ("prefix", prefix)
289− ("delimiter", delimiter),
232+ , ("delimiter", delimiter)
290− ("max-keys", show <$> maxKeys)
233+ , ("max-keys", show <$> maxKeys)
291 ]
234 ]
292
235
293-- | List objects in a bucket matching prefix up to delimiter,
236-- | List objects in a bucket matching prefix up to delimiter,
294-- starting from nextToken.
237-- starting from nextToken.
295−listObjects' ::
238+listObjects' :: Bucket -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Int
296− Bucket ->
297− Maybe Text ->
298− Maybe Text ->
299− Maybe Text ->
300− Maybe Int ->
301− Minio ListObjectsResult
239+ -> Minio ListObjectsResult
302listObjects' bucket prefix nextToken delimiter maxKeys = do
240listObjects' bucket prefix nextToken delimiter maxKeys = do
303− resp <-
304− executeRequest $
305− defaultS3ReqInfo
306− { riMethod = HT.methodGet,
241+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodGet
307− riBucket = Just bucket,
242+ , riBucket = Just bucket
308− riQueryParams = mkOptionalParams params
243+ , riQueryParams = mkOptionalParams params
309− }
244+ }
310 parseListObjectsResponse $ NC.responseBody resp
245 parseListObjectsResponse $ NC.responseBody resp
311 where
246 where
312− params =
247+ params = [
313− [ ("list-type", Just "2"),
248+ ("list-type", Just "2")
314− ("continuation_token", nextToken),
249+ , ("continuation_token", nextToken)
315− ("prefix", prefix),
250+ , ("prefix", prefix)
316− ("delimiter", delimiter),
251+ , ("delimiter", delimiter)
317− ("max-keys", show <$> maxKeys)
252+ , ("max-keys", show <$> maxKeys)
318 ]
253 ]
319
254
320-- | DELETE a bucket from the service.
255-- | DELETE a bucket from the service.
321deleteBucket :: Bucket -> Minio ()
256deleteBucket :: Bucket -> Minio ()
322−deleteBucket bucket =
257+deleteBucket bucket = void $
323− void $
258+ executeRequest $
324− executeRequest $
325− defaultS3ReqInfo
326− { riMethod = HT.methodDelete,
259+ defaultS3ReqInfo { riMethod = HT.methodDelete
327− riBucket = Just bucket
260+ , riBucket = Just bucket
328 }
261 }
329
262
330-- | DELETE an object from the service.
263-- | DELETE an object from the service.
331deleteObject :: Bucket -> Object -> Minio ()
264deleteObject :: Bucket -> Object -> Minio ()
332−deleteObject bucket object =
265+deleteObject bucket object = void $
333− void $
266+ executeRequest $
334− executeRequest $
335− defaultS3ReqInfo
336− { riMethod = HT.methodDelete,
267+ defaultS3ReqInfo { riMethod = HT.methodDelete
337− riBucket = Just bucket,
268+ , riBucket = Just bucket
338− riObject = Just object
269+ , riObject = Just object
339 }
270 }
340
271
341-- | Create a new multipart upload.
272-- | Create a new multipart upload.
342newMultipartUpload :: Bucket -> Object -> [HT.Header] -> Minio UploadId
273newMultipartUpload :: Bucket -> Object -> [HT.Header] -> Minio UploadId
343newMultipartUpload bucket object headers = do
274newMultipartUpload bucket object headers = do
344− resp <-
345− executeRequest $
346− defaultS3ReqInfo
347− { riMethod = HT.methodPost,
275+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodPost
348− riBucket = Just bucket,
276+ , riBucket = Just bucket
349− riObject = Just object,
277+ , riObject = Just object
350− riQueryParams = [("uploads", Nothing)],
278+ , riQueryParams = [("uploads", Nothing)]
351− riHeaders = headers
279+ , riHeaders = headers
352− }
280+ }
353 parseNewMultipartUpload $ NC.responseBody resp
281 parseNewMultipartUpload $ NC.responseBody resp
354
282
355-- | PUT a part of an object as part of a multipart upload.
283-- | PUT a part of an object as part of a multipart upload.
356−putObjectPart ::
284+putObjectPart :: Bucket -> Object -> UploadId -> PartNumber -> [HT.Header]
357− Bucket ->
358− Object ->
359− UploadId ->
360− PartNumber ->
361− [HT.Header] ->
362− Payload ->
363− Minio PartTuple
285+ -> Payload -> Minio PartTuple
364putObjectPart bucket object uploadId partNumber headers payload = do
286putObjectPart bucket object uploadId partNumber headers payload = do
365 -- transform payload to conduit to enable streaming signature
287 -- transform payload to conduit to enable streaming signature
366 let payload' = mkStreamingPayload payload
288 let payload' = mkStreamingPayload payload
367− resp <-
368− executeRequest $
289+ resp <- executeRequest $
369− defaultS3ReqInfo
370− { riMethod = HT.methodPut,
290+ defaultS3ReqInfo { riMethod = HT.methodPut
371− riBucket = Just bucket,
291+ , riBucket = Just bucket
372− riObject = Just object,
292+ , riObject = Just object
373− riQueryParams = mkOptionalParams params,
293+ , riQueryParams = mkOptionalParams params
374− riHeaders = headers,
294+ , riHeaders = headers
375− riPayload = payload'
295+ , riPayload = payload'
376− }
296+ }
377 let rheaders = NC.responseHeaders resp
297 let rheaders = NC.responseHeaders resp
378 etag = getETagHeader rheaders
298 etag = getETagHeader rheaders
379 maybe
299 maybe
380 (throwIO MErrVETagHeaderNotFound)
300 (throwIO MErrVETagHeaderNotFound)
381− (return . (partNumber,))
301+ (return . (partNumber, )) etag
382− etag
383 where
302 where
384− params =
303+ params = [
385− [ ("uploadId", Just uploadId),
304+ ("uploadId", Just uploadId)
386− ("partNumber", Just $ show partNumber)
305+ , ("partNumber", Just $ show partNumber)
387 ]
306 ]
388
307
389srcInfoToHeaders :: SourceInfo -> [HT.Header]
308srcInfoToHeaders :: SourceInfo -> [HT.Header]
390−srcInfoToHeaders srcInfo =
309+srcInfoToHeaders srcInfo = ("x-amz-copy-source",
391− ( "x-amz-copy-source",
392− encodeUtf8 $
393− T.concat
394− [ "/",
395− srcBucket srcInfo,
310+ toS $ T.concat ["/", srcBucket srcInfo,
396− "/",
397− srcObject srcInfo
311+ "/", srcObject srcInfo]
398− ]
399− )
400− : rangeHdr
401− ++ zip names values
312+ ) : rangeHdr ++ zip names values
402 where
313 where
403− names =
404− [ "x-amz-copy-source-if-match",
405− "x-amz-copy-source-if-none-match",
314+ names = ["x-amz-copy-source-if-match", "x-amz-copy-source-if-none-match",
406− "x-amz-copy-source-if-unmodified-since",
315+ "x-amz-copy-source-if-unmodified-since",
407− "x-amz-copy-source-if-modified-since"
316+ "x-amz-copy-source-if-modified-since"]
408− ]
409− values =
410− mapMaybe
411− (fmap encodeUtf8 . (srcInfo &))
317+ values = mapMaybe (fmap encodeUtf8 . (srcInfo &))
412− [ srcIfMatch,
413− srcIfNoneMatch,
318+ [srcIfMatch, srcIfNoneMatch,
414− fmap formatRFC1123 . srcIfUnmodifiedSince,
319+ fmap formatRFC1123 . srcIfUnmodifiedSince,
415− fmap formatRFC1123 . srcIfModifiedSince
320+ fmap formatRFC1123 . srcIfModifiedSince]
416− ]
417− rangeHdr =
418− maybe [] ((\a -> [("x-amz-copy-source-range", HT.renderByteRanges [a])]) . toByteRange) (srcRange srcInfo)
321+ rangeHdr = maybe [] (\a -> [("x-amz-copy-source-range", HT.renderByteRanges [a])])
322+ $ toByteRange <$> srcRange srcInfo
419 toByteRange :: (Int64, Int64) -> HT.ByteRange
323 toByteRange :: (Int64, Int64) -> HT.ByteRange
420 toByteRange (x, y) = HT.ByteRangeFromTo (fromIntegral x) (fromIntegral y)
324 toByteRange (x, y) = HT.ByteRangeFromTo (fromIntegral x) (fromIntegral y)
421
325
422-- | Performs server-side copy of an object or part of an object as an
326-- | Performs server-side copy of an object or part of an object as an
423-- upload part of an ongoing multi-part upload.
327-- upload part of an ongoing multi-part upload.
424−copyObjectPart ::
425− DestinationInfo ->
328+copyObjectPart :: DestinationInfo -> SourceInfo -> UploadId
426− SourceInfo ->
427− UploadId ->
428− PartNumber ->
429− [HT.Header] ->
430− Minio (ETag, UTCTime)
329+ -> PartNumber -> [HT.Header] -> Minio (ETag, UTCTime)
431copyObjectPart dstInfo srcInfo uploadId partNumber headers = do
330copyObjectPart dstInfo srcInfo uploadId partNumber headers = do
432− resp <-
433− executeRequest $
331+ resp <- executeRequest $
434− defaultS3ReqInfo
435− { riMethod = HT.methodPut,
332+ defaultS3ReqInfo { riMethod = HT.methodPut
436− riBucket = Just $ dstBucket dstInfo,
333+ , riBucket = Just $ dstBucket dstInfo
437− riObject = Just $ dstObject dstInfo,
334+ , riObject = Just $ dstObject dstInfo
438− riQueryParams = mkOptionalParams params,
335+ , riQueryParams = mkOptionalParams params
439− riHeaders = headers ++ srcInfoToHeaders srcInfo
336+ , riHeaders = headers ++ srcInfoToHeaders srcInfo
440− }
337+ }
441
338
442 parseCopyObjectResponse $ NC.responseBody resp
339 parseCopyObjectResponse $ NC.responseBody resp
443 where
340 where
444− params =
341+ params = [
445− [ ("uploadId", Just uploadId),
342+ ("uploadId", Just uploadId)
446− ("partNumber", Just $ show partNumber)
343+ , ("partNumber", Just $ show partNumber)
447 ]
344 ]
448
345
449-- | Performs server-side copy of an object that is upto 5GiB in
346-- | Performs server-side copy of an object that is upto 5GiB in
450-- size. If the object is greater than 5GiB, this function throws the
347-- size. If the object is greater than 5GiB, this function throws the
451-- error returned by the server.
348-- error returned by the server.
452−copyObjectSingle ::
349+copyObjectSingle :: Bucket -> Object -> SourceInfo -> [HT.Header]
453− Bucket ->
454− Object ->
455− SourceInfo ->
456− [HT.Header] ->
457− Minio (ETag, UTCTime)
350+ -> Minio (ETag, UTCTime)
458copyObjectSingle bucket object srcInfo headers = do
351copyObjectSingle bucket object srcInfo headers = do
459 -- validate that srcRange is Nothing for this API.
352 -- validate that srcRange is Nothing for this API.
460 when (isJust $ srcRange srcInfo) $
353 when (isJust $ srcRange srcInfo) $
461 throwIO MErrVCopyObjSingleNoRangeAccepted
354 throwIO MErrVCopyObjSingleNoRangeAccepted
462− resp <-
463− executeRequest $
355+ resp <- executeRequest $
464− defaultS3ReqInfo
465− { riMethod = HT.methodPut,
356+ defaultS3ReqInfo { riMethod = HT.methodPut
466− riBucket = Just bucket,
357+ , riBucket = Just bucket
467− riObject = Just object,
358+ , riObject = Just object
468− riHeaders = headers ++ srcInfoToHeaders srcInfo
359+ , riHeaders = headers ++ srcInfoToHeaders srcInfo
469− }
360+ }
470 parseCopyObjectResponse $ NC.responseBody resp
361 parseCopyObjectResponse $ NC.responseBody resp
471
362
472-- | Complete a multipart upload.
363-- | Complete a multipart upload.
473−completeMultipartUpload ::
364+completeMultipartUpload :: Bucket -> Object -> UploadId -> [PartTuple]
474− Bucket ->
475− Object ->
476− UploadId ->
477− [PartTuple] ->
478− Minio ETag
365+ -> Minio ETag
479completeMultipartUpload bucket object uploadId partTuple = do
366completeMultipartUpload bucket object uploadId partTuple = do
480− resp <-
481− executeRequest $
367+ resp <- executeRequest $
482− defaultS3ReqInfo
483− { riMethod = HT.methodPost,
368+ defaultS3ReqInfo { riMethod = HT.methodPost
484− riBucket = Just bucket,
369+ , riBucket = Just bucket
485− riObject = Just object,
370+ , riObject = Just object
486− riQueryParams = mkOptionalParams params,
371+ , riQueryParams = mkOptionalParams params
487− riPayload =
372+ , riPayload = PayloadBS $
488− PayloadBS $
489− mkCompleteMultipartUploadRequest partTuple
373+ mkCompleteMultipartUploadRequest partTuple
490− }
374+ }
491 parseCompleteMultipartUploadResponse $ NC.responseBody resp
375 parseCompleteMultipartUploadResponse $ NC.responseBody resp
492 where
376 where
493 params = [("uploadId", Just uploadId)]
377 params = [("uploadId", Just uploadId)]
494
378
495-- | Abort a multipart upload.
379-- | Abort a multipart upload.
496abortMultipartUpload :: Bucket -> Object -> UploadId -> Minio ()
380abortMultipartUpload :: Bucket -> Object -> UploadId -> Minio ()
497−abortMultipartUpload bucket object uploadId =
381+abortMultipartUpload bucket object uploadId = void $
498− void $
499− executeRequest $
500− defaultS3ReqInfo
501− { riMethod = HT.methodDelete,
382+ executeRequest $ defaultS3ReqInfo { riMethod = HT.methodDelete
502− riBucket = Just bucket,
383+ , riBucket = Just bucket
503− riObject = Just object,
384+ , riObject = Just object
504− riQueryParams = mkOptionalParams params
385+ , riQueryParams = mkOptionalParams params
505− }
386+ }
506 where
387 where
507 params = [("uploadId", Just uploadId)]
388 params = [("uploadId", Just uploadId)]
508
389
509-- | List incomplete multipart uploads.
390-- | List incomplete multipart uploads.
510−listIncompleteUploads' ::
391+listIncompleteUploads' :: Bucket -> Maybe Text -> Maybe Text -> Maybe Text
511− Bucket ->
512− Maybe Text ->
513− Maybe Text ->
514− Maybe Text ->
515− Maybe Text ->
516− Maybe Int ->
517− Minio ListUploadsResult
392+ -> Maybe Text -> Maybe Int -> Minio ListUploadsResult
518listIncompleteUploads' bucket prefix delimiter keyMarker uploadIdMarker maxKeys = do
393listIncompleteUploads' bucket prefix delimiter keyMarker uploadIdMarker maxKeys = do
519− resp <-
520− executeRequest $
521− defaultS3ReqInfo
522− { riMethod = HT.methodGet,
394+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodGet
523− riBucket = Just bucket,
395+ , riBucket = Just bucket
524− riQueryParams = params
396+ , riQueryParams = params
525− }
397+ }
526 parseListUploadsResponse $ NC.responseBody resp
398 parseListUploadsResponse $ NC.responseBody resp
527 where
399 where
528 -- build query params
400 -- build query params
529− params =
530− ("uploads", Nothing)
531− : mkOptionalParams
401+ params = ("uploads", Nothing) : mkOptionalParams
532− [ ("prefix", prefix),
402+ [ ("prefix", prefix)
533− ("delimiter", delimiter),
403+ , ("delimiter", delimiter)
534− ("key-marker", keyMarker),
404+ , ("key-marker", keyMarker)
535− ("upload-id-marker", uploadIdMarker),
405+ , ("upload-id-marker", uploadIdMarker)
536− ("max-uploads", show <$> maxKeys)
406+ , ("max-uploads", show <$> maxKeys)
537− ]
407+ ]
408+
538
409
539-- | List parts of an ongoing multipart upload.
410-- | List parts of an ongoing multipart upload.
540−listIncompleteParts' ::
411+listIncompleteParts' :: Bucket -> Object -> UploadId -> Maybe Text
541− Bucket ->
542− Object ->
543− UploadId ->
544− Maybe Text ->
545− Maybe Text ->
546− Minio ListPartsResult
412+ -> Maybe Text -> Minio ListPartsResult
547listIncompleteParts' bucket object uploadId maxParts partNumMarker = do
413listIncompleteParts' bucket object uploadId maxParts partNumMarker = do
548− resp <-
549− executeRequest $
550− defaultS3ReqInfo
551− { riMethod = HT.methodGet,
414+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodGet
552− riBucket = Just bucket,
415+ , riBucket = Just bucket
553− riObject = Just object,
416+ , riObject = Just object
554− riQueryParams = mkOptionalParams params
417+ , riQueryParams = mkOptionalParams params
555− }
418+ }
556 parseListPartsResponse $ NC.responseBody resp
419 parseListPartsResponse $ NC.responseBody resp
557 where
420 where
558 -- build optional query params
421 -- build optional query params
559− params =
422+ params = [
560− [ ("uploadId", Just uploadId),
423+ ("uploadId", Just uploadId)
561− ("part-number-marker", partNumMarker),
424+ , ("part-number-marker", partNumMarker)
562− ("max-parts", maxParts)
425+ , ("max-parts", maxParts)
563 ]
426 ]
564
427
565-- | Get metadata of an object.
428-- | Get metadata of an object.
566headObject :: Bucket -> Object -> [HT.Header] -> Minio ObjectInfo
429headObject :: Bucket -> Object -> [HT.Header] -> Minio ObjectInfo
567headObject bucket object reqHeaders = do
430headObject bucket object reqHeaders = do
568− resp <-
569− executeRequest $
570− defaultS3ReqInfo
571− { riMethod = HT.methodHead,
431+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodHead
572− riBucket = Just bucket,
432+ , riBucket = Just bucket
573− riObject = Just object,
433+ , riObject = Just object
574− riHeaders =
575− reqHeaders
434+ , riHeaders = reqHeaders
576− -- This header is required for safety as otherwise http-client,
577− -- sends Accept-Encoding: gzip, and the server may actually gzip
578− -- body. In that case Content-Length header will be missing.
579− <> [("Accept-Encoding", "identity")]
580− }
435+ }
436+
581 maybe (throwIO MErrVInvalidObjectInfoResponse) return $
437 maybe (throwIO MErrVInvalidObjectInfoResponse) return $
582− parseGetObjectHeaders object $
438+ parseGetObjectHeaders object $ NC.responseHeaders resp
583− NC.responseHeaders resp
439+
584
440
585-- | Query the object store if a given bucket exists.
441-- | Query the object store if a given bucket exists.
586headBucket :: Bucket -> Minio Bool
442headBucket :: Bucket -> Minio Bool
587−headBucket bucket =
443+headBucket bucket = headBucketEx `catches`
588− headBucketEx
589− `catches` [ Handler handleNoSuchBucket,
444+ [ Handler handleNoSuchBucket
590− Handler handleStatus404
445+ , Handler handleStatus404
591− ]
446+ ]
447+
592 where
448 where
593 handleNoSuchBucket :: ServiceErr -> Minio Bool
449 handleNoSuchBucket :: ServiceErr -> Minio Bool
594− handleNoSuchBucket e
595− | e == NoSuchBucket = return False
450+ handleNoSuchBucket e | e == NoSuchBucket = return False
596− | otherwise = throwIO e
451+ | otherwise = throwIO e
452+
597 handleStatus404 :: NC.HttpException -> Minio Bool
453 handleStatus404 :: NC.HttpException -> Minio Bool
598 handleStatus404 e@(NC.HttpExceptionRequest _ (NC.StatusCodeException res _)) =
454 handleStatus404 e@(NC.HttpExceptionRequest _ (NC.StatusCodeException res _)) =
599 if NC.responseStatus res == status404
455 if NC.responseStatus res == status404
600− then return False
456+ then return False
601− else throwIO e
457+ else throwIO e
602 handleStatus404 e = throwIO e
458 handleStatus404 e = throwIO e
459+
603 headBucketEx = do
460 headBucketEx = do
604− resp <-
605− executeRequest $
606− defaultS3ReqInfo
607− { riMethod = HT.methodHead,
461+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodHead
608− riBucket = Just bucket
462+ , riBucket = Just bucket
609− }
463+ }
610 return $ NC.responseStatus resp == HT.ok200
464 return $ NC.responseStatus resp == HT.ok200
611
465
612-- | Set the notification configuration on a bucket.
466-- | Set the notification configuration on a bucket.
613putBucketNotification :: Bucket -> Notification -> Minio ()
467putBucketNotification :: Bucket -> Notification -> Minio ()
614putBucketNotification bucket ncfg = do
468putBucketNotification bucket ncfg = do
615 ns <- asks getSvcNamespace
469 ns <- asks getSvcNamespace
616− void $
617− executeRequest $
618− defaultS3ReqInfo
619− { riMethod = HT.methodPut,
470+ void $ executeRequest $ defaultS3ReqInfo { riMethod = HT.methodPut
620− riBucket = Just bucket,
471+ , riBucket = Just bucket
621− riQueryParams = [("notification", Nothing)],
472+ , riQueryParams = [("notification", Nothing)]
622− riPayload =
473+ , riPayload = PayloadBS $
623− PayloadBS $
624− mkPutNotificationRequest ns ncfg
474+ mkPutNotificationRequest ns ncfg
625− }
475+ }
626
476
627-- | Retrieve the notification configuration on a bucket.
477-- | Retrieve the notification configuration on a bucket.
628getBucketNotification :: Bucket -> Minio Notification
478getBucketNotification :: Bucket -> Minio Notification
629getBucketNotification bucket = do
479getBucketNotification bucket = do
630− resp <-
631− executeRequest $
632− defaultS3ReqInfo
633− { riMethod = HT.methodGet,
480+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodGet
634− riBucket = Just bucket,
481+ , riBucket = Just bucket
635− riQueryParams = [("notification", Nothing)]
482+ , riQueryParams = [("notification", Nothing)]
636− }
483+ }
637 parseNotification $ NC.responseBody resp
484 parseNotification $ NC.responseBody resp
638
485
639-- | Remove all notifications configured on a bucket.
486-- | Remove all notifications configured on a bucket.
@@ -643,14 +490,11 @@ removeAllBucketNotification = flip putBucketNotification defaultNotification
643-- | Fetch the policy if any on a bucket.
490-- | Fetch the policy if any on a bucket.
644getBucketPolicy :: Bucket -> Minio Text
491getBucketPolicy :: Bucket -> Minio Text
645getBucketPolicy bucket = do
492getBucketPolicy bucket = do
646− resp <-
647− executeRequest $
648− defaultS3ReqInfo
649− { riMethod = HT.methodGet,
493+ resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodGet
650− riBucket = Just bucket,
494+ , riBucket = Just bucket
651− riQueryParams = [("policy", Nothing)]
495+ , riQueryParams = [("policy", Nothing)]
652− }
496+ }
653− return $ decodeUtf8Lenient $ toStrictBS $ NC.responseBody resp
497+ return $ toS $ NC.responseBody resp
654
498
655-- | Set a new policy on a bucket.
499-- | Set a new policy on a bucket.
656-- As a special condition if the policy is empty
500-- As a special condition if the policy is empty
@@ -662,24 +506,18 @@ setBucketPolicy bucket policy = do
662 else putBucketPolicy bucket policy
506 else putBucketPolicy bucket policy
663
507
664-- | Save a new policy on a bucket.
508-- | Save a new policy on a bucket.
665−putBucketPolicy :: Bucket -> Text -> Minio ()
509+putBucketPolicy :: Bucket -> Text -> Minio()
666putBucketPolicy bucket policy = do
510putBucketPolicy bucket policy = do
667− void $
668− executeRequest $
669− defaultS3ReqInfo
670− { riMethod = HT.methodPut,
511+ void $ executeRequest $ defaultS3ReqInfo { riMethod = HT.methodPut
671− riBucket = Just bucket,
512+ , riBucket = Just bucket
672− riQueryParams = [("policy", Nothing)],
513+ , riQueryParams = [("policy", Nothing)]
673− riPayload = PayloadBS $ encodeUtf8 policy
514+ , riPayload = PayloadBS $ encodeUtf8 policy
674− }
515+ }
675
516
676-- | Delete any policy set on a bucket.
517-- | Delete any policy set on a bucket.
677−deleteBucketPolicy :: Bucket -> Minio ()
518+deleteBucketPolicy :: Bucket -> Minio()
678deleteBucketPolicy bucket = do
519deleteBucketPolicy bucket = do
679− void $
680− executeRequest $
681− defaultS3ReqInfo
682− { riMethod = HT.methodDelete,
520+ void $ executeRequest $ defaultS3ReqInfo { riMethod = HT.methodDelete
683− riBucket = Just bucket,
521+ , riBucket = Just bucket
684− riQueryParams = [("policy", Nothing)]
522+ , riQueryParams = [("policy", Nothing)]
685− }
523+ }
src/Network/Minio/SelectAPI.hs+238−233
@@ -15,103 +15,113 @@
15--
15--
16
16
17module Network.Minio.SelectAPI
17module Network.Minio.SelectAPI
18+ (
19+
18− ( -- | The `selectObjectContent` allows querying CSV, JSON or Parquet
20+ -- | The `selectObjectContent` allows querying CSV, JSON or Parquet
19− -- format objects in AWS S3 and in MinIO using SQL Select
21+ -- format objects in AWS S3 and in MinIO using SQL Select
20− -- statements. This allows significant reduction of data transfer
22+ -- statements. This allows significant reduction of data transfer
21− -- from object storage for computation-intensive tasks, as relevant
23+ -- from object storage for computation-intensive tasks, as relevant
22− -- data is filtered close to the storage.
24+ -- data is filtered close to the storage.
25+
23− selectObjectContent,
26+ selectObjectContent
27+
24− SelectRequest,
28+ , SelectRequest
25− selectRequest,
29+ , selectRequest
26−
30+
27− -- *** Input Serialization
31+ -- *** Input Serialization
32+
28− InputSerialization,
33+ , InputSerialization
29− defaultCsvInput,
34+ , defaultCsvInput
30− linesJsonInput,
35+ , linesJsonInput
31− documentJsonInput,
36+ , documentJsonInput
32− defaultParquetInput,
37+ , defaultParquetInput
33− setInputCSVProps,
38+ , setInputCSVProps
39+
34− CompressionType (..),
40+ , CompressionType(..)
35− setInputCompressionType,
41+ , setInputCompressionType
36−
42+
37− -- *** CSV Format details
43+ -- *** CSV Format details
38−
44+
39− -- | CSV format options such as delimiters and quote characters are
45+ -- | CSV format options such as delimiters and quote characters are
40− -- specified using using the functions below. Options are combined
46+ -- specified using using the functions below. Options are combined
41− -- monoidally.
47+ -- monoidally.
48+
42− CSVProp,
49+ , CSVProp
43− recordDelimiter,
50+ , recordDelimiter
44− fieldDelimiter,
51+ , fieldDelimiter
45− quoteCharacter,
52+ , quoteCharacter
46− quoteEscapeCharacter,
53+ , quoteEscapeCharacter
47− commentCharacter,
54+ , commentCharacter
48− allowQuotedRecordDelimiter,
55+ , allowQuotedRecordDelimiter
49− FileHeaderInfo (..),
56+ , FileHeaderInfo(..)
50− fileHeaderInfo,
57+ , fileHeaderInfo
51− QuoteFields (..),
58+ , QuoteFields(..)
52− quoteFields,
59+ , quoteFields
53−
60+
54− -- *** Output Serialization
61+ -- *** Output Serialization
62+
55− OutputSerialization,
63+ , OutputSerialization
56− defaultCsvOutput,
64+ , defaultCsvOutput
57− defaultJsonOutput,
65+ , defaultJsonOutput
58− outputCSVFromProps,
66+ , outputCSVFromProps
59− outputJSONFromRecordDelimiter,
67+ , outputJSONFromRecordDelimiter
60−
68+
61− -- *** Progress messages
69+ -- *** Progress messages
62− setRequestProgressEnabled,
70+
63−
71+ , setRequestProgressEnabled
72+
64− -- *** Interpreting Select output
73+ -- *** Interpreting Select output
65−
74+
66− -- | The conduit returned by `selectObjectContent` returns values of
75+ -- | The conduit returned by `selectObjectContent` returns values of
67− -- the `EventMessage` data type. This returns the query output
76+ -- the `EventMessage` data type. This returns the query output
68− -- messages formatted according to the chosen output serialization,
77+ -- messages formatted according to the chosen output serialization,
69− -- interleaved with progress messages (if enabled by
78+ -- interleaved with progress messages (if enabled by
70− -- `setRequestProgressEnabled`), and at the end a statistics
79+ -- `setRequestProgressEnabled`), and at the end a statistics
71− -- message.
80+ -- message.
72− --
81+ --
73− -- If the application is interested in only the payload, then
82+ -- If the application is interested in only the payload, then
74− -- `getPayloadBytes` can be used. For example to simply print the
83+ -- `getPayloadBytes` can be used. For example to simply print the
75− -- payload to stdout:
84+ -- payload to stdout:
76− --
85+ --
77− -- > resultConduit <- selectObjectContent bucket object mySelectRequest
86+ -- > resultConduit <- selectObjectContent bucket object mySelectRequest
78− -- > runConduit $ resultConduit .| getPayloadBytes .| stdoutC
87+ -- > runConduit $ resultConduit .| getPayloadBytes .| stdoutC
79− --
88+ --
80− -- Note that runConduit, the connect operator (.|) and stdoutC are
89+ -- Note that runConduit, the connect operator (.|) and stdoutC are
81− -- all from the "conduit" package.
90+ -- all from the "conduit" package.
91+
82− getPayloadBytes,
92+ , getPayloadBytes
83− EventMessage (..),
93+ , EventMessage(..)
84− Progress (..),
94+ , Progress(..)
85− Stats,
95+ , Stats
86− )
87−where
96+ ) where
88−
97+
89−import Conduit ((.|))
98+import Conduit ((.|))
90−import qualified Conduit as C
99+import qualified Conduit as C
91−import qualified Data.Binary as Bin
100+import qualified Data.Binary as Bin
92−import qualified Data.ByteString as B
101+import qualified Data.ByteString as B
93−import qualified Data.ByteString.Lazy as LB
102+import qualified Data.ByteString.Lazy as LB
94−import Data.Digest.CRC32 (crc32, crc32Update)
103+import Data.Digest.CRC32 (crc32, crc32Update)
95−import Lib.Prelude
96−import qualified Network.HTTP.Conduit as NC
104+import qualified Network.HTTP.Conduit as NC
97−import qualified Network.HTTP.Types as HT
105+import qualified Network.HTTP.Types as HT
106+import UnliftIO (MonadUnliftIO)
107+
108+import Lib.Prelude
109+
98−import Network.Minio.API
110+import Network.Minio.API
99−import Network.Minio.Data
111+import Network.Minio.Data
100−import Network.Minio.Errors
112+import Network.Minio.Errors
101−import Network.Minio.Utils
113+import Network.Minio.Utils
102−import Network.Minio.XmlGenerator
114+import Network.Minio.XmlGenerator
103−import Network.Minio.XmlParser
115+import Network.Minio.XmlParser
104−import UnliftIO (MonadUnliftIO)
116+
105−
106−data EventStreamException
117+data EventStreamException = ESEPreludeCRCFailed
107− = ESEPreludeCRCFailed
108− | ESEMessageCRCFailed
118+ | ESEMessageCRCFailed
109− | ESEUnexpectedEndOfStream
119+ | ESEUnexpectedEndOfStream
110− | ESEDecodeFail [Char]
120+ | ESEDecodeFail [Char]
111− | ESEInvalidHeaderType
121+ | ESEInvalidHeaderType
112− | ESEInvalidHeaderValueType
122+ | ESEInvalidHeaderValueType
113− | ESEInvalidMessageType
123+ | ESEInvalidMessageType
114− deriving stock (Eq, Show)
124+ deriving (Eq, Show)
115
125
116instance Exception EventStreamException
126instance Exception EventStreamException
117
127
@@ -119,176 +129,171 @@ instance Exception EventStreamException
119chunkSize :: Int
129chunkSize :: Int
120chunkSize = 32 * 1024
130chunkSize = 32 * 1024
121
131
122−parseBinary :: (Bin.Binary a) => ByteString -> IO a
132+parseBinary :: Bin.Binary a => ByteString -> IO a
123parseBinary b = do
133parseBinary b = do
124− case Bin.decodeOrFail $ LB.fromStrict b of
134+ case Bin.decodeOrFail $ LB.fromStrict b of
125− Left (_, _, msg) -> throwIO $ ESEDecodeFail msg
135+ Left (_, _, msg) -> throwIO $ ESEDecodeFail msg
126− Right (_, _, r) -> return r
136+ Right (_, _, r) -> return r
127
137
128bytesToHeaderName :: Text -> IO MsgHeaderName
138bytesToHeaderName :: Text -> IO MsgHeaderName
129bytesToHeaderName t = case t of
139bytesToHeaderName t = case t of
130− ":message-type" -> return MessageType
140+ ":message-type" -> return MessageType
131− ":event-type" -> return EventType
141+ ":event-type" -> return EventType
132− ":content-type" -> return ContentType
142+ ":content-type" -> return ContentType
133− ":error-code" -> return ErrorCode
143+ ":error-code" -> return ErrorCode
134 ":error-message" -> return ErrorMessage
144 ":error-message" -> return ErrorMessage
135− _ -> throwIO ESEInvalidHeaderType
145+ _ -> throwIO ESEInvalidHeaderType
136
146
137−parseHeaders ::
138− (MonadUnliftIO m) =>
147+parseHeaders :: MonadUnliftIO m
139− Word32 ->
140− C.ConduitM ByteString a m [MessageHeader]
148+ => Word32 -> C.ConduitM ByteString a m [MessageHeader]
141parseHeaders 0 = return []
149parseHeaders 0 = return []
142parseHeaders hdrLen = do
150parseHeaders hdrLen = do
143− bs1 <- readNBytes 1
151+ bs1 <- readNBytes 1
144− n :: Word8 <- liftIO $ parseBinary bs1
152+ n :: Word8 <- liftIO $ parseBinary bs1
145
153
146− headerKeyBytes <- readNBytes $ fromIntegral n
154+ headerKeyBytes <- readNBytes $ fromIntegral n
147− let headerKey = decodeUtf8Lenient headerKeyBytes
155+ let headerKey = decodeUtf8Lenient headerKeyBytes
148− headerName <- liftIO $ bytesToHeaderName headerKey
156+ headerName <- liftIO $ bytesToHeaderName headerKey
149
157
150− bs2 <- readNBytes 1
158+ bs2 <- readNBytes 1
151− headerValueType :: Word8 <- liftIO $ parseBinary bs2
159+ headerValueType :: Word8 <- liftIO $ parseBinary bs2
152− when (headerValueType /= 7) $ throwIO ESEInvalidHeaderValueType
160+ when (headerValueType /= 7) $ throwIO ESEInvalidHeaderValueType
153
161
154− bs3 <- readNBytes 2
162+ bs3 <- readNBytes 2
155− vLen :: Word16 <- liftIO $ parseBinary bs3
163+ vLen :: Word16 <- liftIO $ parseBinary bs3
156− headerValueBytes <- readNBytes $ fromIntegral vLen
164+ headerValueBytes <- readNBytes $ fromIntegral vLen
157− let headerValue = decodeUtf8Lenient headerValueBytes
165+ let headerValue = decodeUtf8Lenient headerValueBytes
158− m = (headerName, headerValue)
166+ m = (headerName, headerValue)
159− k = 1 + fromIntegral n + 1 + 2 + fromIntegral vLen
167+ k = 1 + fromIntegral n + 1 + 2 + fromIntegral vLen
160
168
161− ms <- parseHeaders (hdrLen - k)
169+ ms <- parseHeaders (hdrLen - k)
162− return (m : ms)
170+ return (m:ms)
163
171
164-- readNBytes returns N bytes read from the string and throws an
172-- readNBytes returns N bytes read from the string and throws an
165-- exception if N bytes are not present on the stream.
173-- exception if N bytes are not present on the stream.
166−readNBytes :: (MonadUnliftIO m) => Int -> C.ConduitM ByteString a m ByteString
174+readNBytes :: MonadUnliftIO m => Int -> C.ConduitM ByteString a m ByteString
167readNBytes n = do
175readNBytes n = do
168− b <- LB.toStrict <$> (C.takeCE n .| C.sinkLazy)
176+ b <- LB.toStrict <$> (C.takeCE n .| C.sinkLazy)
169− if B.length b /= n
177+ if B.length b /= n
170− then throwIO ESEUnexpectedEndOfStream
178+ then throwIO ESEUnexpectedEndOfStream
171− else return b
179+ else return b
172−
180+
173−crcCheck ::
174− (MonadUnliftIO m) =>
181+crcCheck :: MonadUnliftIO m
175− C.ConduitM ByteString ByteString m ()
182+ => C.ConduitM ByteString ByteString m ()
176crcCheck = do
183crcCheck = do
177− b <- readNBytes 12
184+ b <- readNBytes 12
178− n :: Word32 <- liftIO $ parseBinary $ B.take 4 b
185+ n :: Word32 <- liftIO $ parseBinary $ B.take 4 b
179− preludeCRC :: Word32 <- liftIO $ parseBinary $ B.drop 8 b
186+ preludeCRC :: Word32 <- liftIO $ parseBinary $ B.drop 8 b
180− when (crc32 (B.take 8 b) /= preludeCRC) $
187+ when (crc32 (B.take 8 b) /= preludeCRC) $
181− throwIO ESEPreludeCRCFailed
188+ throwIO ESEPreludeCRCFailed
182
189
183− -- we do not yield the checksum
190+ -- we do not yield the checksum
184− C.yield $ B.take 8 b
191+ C.yield $ B.take 8 b
185
192
186− -- 12 bytes have been read off the current message. Now read the
193+ -- 12 bytes have been read off the current message. Now read the
187− -- next (n-12)-4 bytes and accumulate the checksum, and yield it.
194+ -- next (n-12)-4 bytes and accumulate the checksum, and yield it.
188− let startCrc = crc32 b
195+ let startCrc = crc32 b
189− finalCrc <- accumulateYield (fromIntegral n - 16) startCrc
196+ finalCrc <- accumulateYield (fromIntegral n-16) startCrc
190
197
191− bs <- readNBytes 4
198+ bs <- readNBytes 4
192− expectedCrc :: Word32 <- liftIO $ parseBinary bs
199+ expectedCrc :: Word32 <- liftIO $ parseBinary bs
193
200
194− when (finalCrc /= expectedCrc) $
201+ when (finalCrc /= expectedCrc) $
195− throwIO ESEMessageCRCFailed
202+ throwIO ESEMessageCRCFailed
196
203
197− -- we unconditionally recurse - downstream figures out when to
204+ -- we unconditionally recurse - downstream figures out when to
198− -- quit reading the stream
205+ -- quit reading the stream
199− crcCheck
206+ crcCheck
200 where
207 where
201 accumulateYield n checkSum = do
208 accumulateYield n checkSum = do
202− let toRead = min n chunkSize
209+ let toRead = min n chunkSize
203− b <- readNBytes toRead
210+ b <- readNBytes toRead
204− let c' = crc32Update checkSum b
211+ let c' = crc32Update checkSum b
205− n' = n - B.length b
212+ n' = n - B.length b
206− C.yield b
213+ C.yield b
207− if n' > 0
214+ if n' > 0
208− then accumulateYield n' c'
215+ then accumulateYield n' c'
209− else return c'
216+ else return c'
210−
217+
211−handleMessage :: (MonadUnliftIO m) => C.ConduitT ByteString EventMessage m ()
218+handleMessage :: MonadUnliftIO m => C.ConduitT ByteString EventMessage m ()
212handleMessage = do
219handleMessage = do
213− b1 <- readNBytes 4
220+ b1 <- readNBytes 4
214− msgLen :: Word32 <- liftIO $ parseBinary b1
221+ msgLen :: Word32 <- liftIO $ parseBinary b1
215−
222+
216− b2 <- readNBytes 4
223+ b2 <- readNBytes 4
217− hdrLen :: Word32 <- liftIO $ parseBinary b2
224+ hdrLen :: Word32 <- liftIO $ parseBinary b2
218−
225+
219− hs <- parseHeaders hdrLen
226+ hs <- parseHeaders hdrLen
220−
227+
221− let payloadLen = msgLen - hdrLen - 16
228+ let payloadLen = msgLen - hdrLen - 16
222− getHdrVal h = fmap snd . find ((h ==) . fst)
229+ getHdrVal h = fmap snd . headMay . filter ((h ==) . fst)
223− eventHdrValue = getHdrVal EventType hs
230+ eventHdrValue = getHdrVal EventType hs
224− msgHdrValue = getHdrVal MessageType hs
231+ msgHdrValue = getHdrVal MessageType hs
225− errCode = getHdrVal ErrorCode hs
232+ errCode = getHdrVal ErrorCode hs
226− errMsg = getHdrVal ErrorMessage hs
233+ errMsg = getHdrVal ErrorMessage hs
227−
234+
228− case msgHdrValue of
235+ case msgHdrValue of
229− Just "event" -> do
236+ Just "event" -> do
230− case eventHdrValue of
237+ case eventHdrValue of
231− Just "Records" -> passThrough $ fromIntegral payloadLen
238+ Just "Records" -> passThrough $ fromIntegral payloadLen
232− Just "Cont" -> return ()
239+ Just "Cont" -> return ()
233− Just "Progress" -> do
240+ Just "Progress" -> do
234− bs <- readNBytes $ fromIntegral payloadLen
241+ bs <- readNBytes $ fromIntegral payloadLen
235− progress <- parseSelectProgress bs
242+ progress <- parseSelectProgress bs
236− C.yield $ ProgressEventMessage progress
243+ C.yield $ ProgressEventMessage progress
237− Just "Stats" -> do
244+ Just "Stats" -> do
238− bs <- readNBytes $ fromIntegral payloadLen
245+ bs <- readNBytes $ fromIntegral payloadLen
239− stats <- parseSelectProgress bs
246+ stats <- parseSelectProgress bs
240− C.yield $ StatsEventMessage stats
247+ C.yield $ StatsEventMessage stats
241− Just "End" -> return ()
248+ Just "End" -> return ()
242− _ -> throwIO ESEInvalidMessageType
249+ _ -> throwIO ESEInvalidMessageType
243− when (eventHdrValue /= Just "End") handleMessage
250+ when (eventHdrValue /= Just "End") handleMessage
251+
244− Just "error" -> do
252+ Just "error" -> do
245− let reqMsgMay = RequestLevelErrorMessage <$> errCode <*> errMsg
253+ let reqMsgMay = RequestLevelErrorMessage <$> errCode <*> errMsg
246− maybe (throwIO ESEInvalidMessageType) C.yield reqMsgMay
254+ maybe (throwIO ESEInvalidMessageType) C.yield reqMsgMay
255+
247− _ -> throwIO ESEInvalidMessageType
256+ _ -> throwIO ESEInvalidMessageType
257+
248 where
258 where
249 passThrough 0 = return ()
259 passThrough 0 = return ()
250 passThrough n = do
260 passThrough n = do
251− let c = min n chunkSize
261+ let c = min n chunkSize
252− b <- readNBytes c
262+ b <- readNBytes c
253− C.yield $ RecordPayloadEventMessage b
263+ C.yield $ RecordPayloadEventMessage b
254− passThrough $ n - B.length b
264+ passThrough $ n - B.length b
255−
265+
266+
256−selectProtoConduit ::
267+selectProtoConduit :: MonadUnliftIO m
257− (MonadUnliftIO m) =>
258− C.ConduitT ByteString EventMessage m ()
268+ => C.ConduitT ByteString EventMessage m ()
259selectProtoConduit = crcCheck .| handleMessage
269selectProtoConduit = crcCheck .| handleMessage
260
270
261-- | selectObjectContent calls the SelectRequest on the given
271-- | selectObjectContent calls the SelectRequest on the given
262-- object. It returns a Conduit of event messages that can be consumed
272-- object. It returns a Conduit of event messages that can be consumed
263-- by the client.
273-- by the client.
264−selectObjectContent ::
274+selectObjectContent :: Bucket -> Object -> SelectRequest
265− Bucket ->
266− Object ->
267− SelectRequest ->
268− Minio (C.ConduitT () EventMessage Minio ())
275+ -> Minio (C.ConduitT () EventMessage Minio ())
269selectObjectContent b o r = do
276selectObjectContent b o r = do
270− let reqInfo =
271− defaultS3ReqInfo
272− { riMethod = HT.methodPost,
277+ let reqInfo = defaultS3ReqInfo { riMethod = HT.methodPost
273− riBucket = Just b,
278+ , riBucket = Just b
274− riObject = Just o,
279+ , riObject = Just o
275− riPayload = PayloadBS $ mkSelectRequest r,
280+ , riPayload = PayloadBS $ mkSelectRequest r
276− riNeedsLocation = False,
281+ , riNeedsLocation = False
277− riQueryParams = [("select", Nothing), ("select-type", Just "2")]
282+ , riQueryParams = [("select", Nothing), ("select-type", Just "2")]
278− }
283+ }
279− -- print $ mkSelectRequest r
284+ --print $ mkSelectRequest r
280− resp <- mkStreamRequest reqInfo
285+ resp <- mkStreamRequest reqInfo
281− return $ NC.responseBody resp .| selectProtoConduit
286+ return $ NC.responseBody resp .| selectProtoConduit
282
287
283-- | A helper conduit that returns only the record payload bytes.
288-- | A helper conduit that returns only the record payload bytes.
284−getPayloadBytes :: (MonadIO m) => C.ConduitT EventMessage ByteString m ()
289+getPayloadBytes :: MonadIO m => C.ConduitT EventMessage ByteString m ()
285getPayloadBytes = do
290getPayloadBytes = do
286− evM <- C.await
291+ evM <- C.await
287− case evM of
292+ case evM of
288− Just v -> do
293+ Just v -> do
289− case v of
294+ case v of
290− RecordPayloadEventMessage b -> C.yield b
295+ RecordPayloadEventMessage b -> C.yield b
291− RequestLevelErrorMessage c m -> liftIO $ throwIO $ SelectErr c m
296+ RequestLevelErrorMessage c m -> liftIO $ throwIO $ SelectErr c m
292− _ -> return ()
297+ _ -> return ()
293− getPayloadBytes
298+ getPayloadBytes
294− Nothing -> return ()
299+ Nothing -> return ()
src/Network/Minio/Sign/V4.hs+327−430
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -13,94 +13,88 @@
13-- See the License for the specific language governing permissions and
13-- See the License for the specific language governing permissions and
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16−{-# LANGUAGE BangPatterns #-}
16+
17−
18−module Network.Minio.Sign.V4
17+module Network.Minio.Sign.V4 where
19− ( SignParams (..),
18+
20− signV4QueryParams,
21− signV4,
22− signV4PostPolicy,
23− signV4Stream,
24− Service (..),
25− credentialScope,
26− )
27−where
28−
29−import qualified Conduit as C
19+import qualified Conduit as C
30−import qualified Data.ByteArray as BA
20+import qualified Data.ByteString as B
31−import qualified Data.ByteString as B
32−import qualified Data.ByteString.Base64 as Base64
21+import qualified Data.ByteString.Base64 as Base64
33−import qualified Data.ByteString.Char8 as B8
22+import qualified Data.ByteString.Char8 as B8
34−import qualified Data.ByteString.Lazy as LB
35−import Data.CaseInsensitive (mk)
23+import Data.CaseInsensitive (mk)
36−import qualified Data.CaseInsensitive as CI
24+import qualified Data.CaseInsensitive as CI
37−import qualified Data.HashMap.Strict as Map
25+import qualified Data.HashMap.Strict as Map
38−import qualified Data.HashSet as Set
26+import qualified Data.HashSet as Set
39−import Data.List (partition)
40−import qualified Data.List.NonEmpty as NE
41−import qualified Data.Time as Time
27+import qualified Data.Time as Time
42−import Lib.Prelude
43−import qualified Network.HTTP.Conduit as NC
28+import qualified Network.HTTP.Conduit as NC
44−import Network.HTTP.Types (Header, SimpleQuery, hContentEncoding, parseQuery)
29+import Network.HTTP.Types (Header, parseQuery)
45−import qualified Network.HTTP.Types as H
30+import qualified Network.HTTP.Types as H
46−import Network.HTTP.Types.Header (RequestHeaders)
47−import Network.Minio.Data.ByteString
48−import Network.Minio.Data.Crypto
49−import Network.Minio.Data.Time
50−import Network.Minio.Errors
51−import Text.Printf (printf)
31+import Text.Printf (printf)
32+
33+import Lib.Prelude
34+
35+import Network.Minio.Data.ByteString
36+import Network.Minio.Data.Crypto
37+import Network.Minio.Data.Time
38+import Network.Minio.Errors
52
39
53-- these headers are not included in the string to sign when signing a
40-- these headers are not included in the string to sign when signing a
54-- request
41-- request
55ignoredHeaders :: Set.HashSet ByteString
42ignoredHeaders :: Set.HashSet ByteString
56−ignoredHeaders =
43+ignoredHeaders = Set.fromList $ map CI.foldedCase
57− Set.fromList $
58− map
59− CI.foldedCase
60− [ H.hAuthorization,
44+ [ H.hAuthorization
61− H.hContentType,
45+ , H.hContentType
62− H.hUserAgent
46+ , H.hUserAgent
63− ]
47+ ]
64−
48+
65−data Service = ServiceS3 | ServiceSTS
49+data SignV4Data = SignV4Data {
66− deriving stock (Eq, Show)
50+ sv4SignTime :: UTCTime
67−
51+ , sv4Scope :: ByteString
68−toByteString :: Service -> ByteString
52+ , sv4CanonicalRequest :: ByteString
69−toByteString ServiceS3 = "s3"
53+ , sv4HeadersToSign :: [(ByteString, ByteString)]
70−toByteString ServiceSTS = "sts"
54+ , sv4Output :: [(ByteString, ByteString)]
71−
55+ , sv4StringToSign :: ByteString
56+ , sv4SigningKey :: ByteString
57+ } deriving (Show)
58+
72−data SignParams = SignParams
59+data SignParams = SignParams {
73− { spAccessKey :: Text,
60+ spAccessKey :: Text
74− spSecretKey :: BA.ScrubbedBytes,
61+ , spSecretKey :: Text
75− spSessionToken :: Maybe BA.ScrubbedBytes,
76− spService :: Service,
77− spTimeStamp :: UTCTime,
62+ , spTimeStamp :: UTCTime
78− spRegion :: Maybe Text,
63+ , spRegion :: Maybe Text
79− spExpirySecs :: Maybe UrlExpiry,
64+ , spExpirySecs :: Maybe Int
80− spPayloadHash :: Maybe ByteString
65+ , spPayloadHash :: Maybe ByteString
81− }
66+ } deriving (Show)
82− deriving stock (Show)
67+
68+debugPrintSignV4Data :: SignV4Data -> IO ()
69+debugPrintSignV4Data (SignV4Data t s cr h2s o sts sk) = do
70+ B8.putStrLn "SignV4Data:"
71+ B8.putStr "Timestamp: " >> print t
72+ B8.putStr "Scope: " >> B8.putStrLn s
73+ B8.putStrLn "Canonical Request:"
74+ B8.putStrLn cr
75+ B8.putStr "Headers to Sign: " >> print h2s
76+ B8.putStr "Output: " >> print o
77+ B8.putStr "StringToSign: " >> B8.putStrLn sts
78+ B8.putStr "SigningKey: " >> printBytes sk
79+ B8.putStrLn "END of SignV4Data ========="
80+ where
81+ printBytes b = do
82+ mapM_ (\x -> B.putStr $ B.concat [show x, " "]) $ B.unpack b
83+ B8.putStrLn ""
83
84
84mkAuthHeader :: Text -> ByteString -> ByteString -> ByteString -> H.Header
85mkAuthHeader :: Text -> ByteString -> ByteString -> ByteString -> H.Header
85mkAuthHeader accessKey scope signedHeaderKeys sign =
86mkAuthHeader accessKey scope signedHeaderKeys sign =
86− let authValue =
87+ let authValue = B.concat
87− B.concat
88− [ "AWS4-HMAC-SHA256 Credential=",
88+ [ "AWS4-HMAC-SHA256 Credential="
89− encodeUtf8 accessKey,
89+ , toS accessKey
90− "/",
90+ , "/"
91− scope,
91+ , scope
92− ", SignedHeaders=",
92+ , ", SignedHeaders="
93− signedHeaderKeys,
93+ , signedHeaderKeys
94− ", Signature=",
94+ , ", Signature="
95− sign
95+ , sign
96− ]
96+ ]
97− in (H.hAuthorization, authValue)
97+ in (H.hAuthorization, authValue)
98−
99−data IsStreaming = IsStreamingLength Int64 | NotStreaming
100− deriving stock (Eq, Show)
101−
102−amzSecurityToken :: ByteString
103−amzSecurityToken = "X-Amz-Security-Token"
104
98
105-- | Given SignParams and request details, including request method,
99-- | Given SignParams and request details, including request method,
106-- request path, headers, query params and payload hash, generates an
100-- request path, headers, query params and payload hash, generates an
@@ -114,212 +108,124 @@ amzSecurityToken = "X-Amz-Security-Token"
114-- is being created. The expiry is interpreted as an integer number of
108-- is being created. The expiry is interpreted as an integer number of
115-- seconds. The output will be the list of query-parameters to add to
109-- seconds. The output will be the list of query-parameters to add to
116-- the request.
110-- the request.
117−signV4QueryParams :: SignParams -> NC.Request -> SimpleQuery
118−signV4QueryParams !sp !req =
119− let scope = credentialScope sp
120− expiry = spExpirySecs sp
121−
122− headersToSign = getHeadersToSign $ NC.requestHeaders req
123− signedHeaderKeys = B.intercalate ";" $ sort $ map fst headersToSign
124− -- query-parameters to be added before signing for presigned URLs
125− -- (i.e. when `isJust expiry`)
126− authQP =
127− [ ("X-Amz-Algorithm", "AWS4-HMAC-SHA256"),
128− ("X-Amz-Credential", B.concat [encodeUtf8 $ spAccessKey sp, "/", scope]),
129− ("X-Amz-Date", awsTimeFormatBS $ spTimeStamp sp),
130− ("X-Amz-Expires", maybe "" showBS expiry),
131− ("X-Amz-SignedHeaders", signedHeaderKeys)
132− ]
133− ++ maybeToList ((amzSecurityToken,) . BA.convert <$> spSessionToken sp)
134− finalQP =
135− parseQuery (NC.queryString req)
136− ++ if isJust expiry
137− then (fmap . fmap) Just authQP
138− else []
139− -- 1. compute canonical request
140− canonicalRequest =
141− mkCanonicalRequest
142− False
143− sp
144− (NC.setQueryString finalQP req)
145− headersToSign
146−
147− -- 2. compute string to sign
148− stringToSign = mkStringToSign (spTimeStamp sp) scope canonicalRequest
149− -- 3.1 compute signing key
150− signingKey = getSigningKey sp
151− -- 3.2 compute signature
152− signature = computeSignature stringToSign signingKey
153− in ("X-Amz-Signature", signature) : authQP
154−
155−-- | Given SignParams and request details, including request method, request
156−-- path, headers, query params and payload hash, generates an updated set of
157−-- headers, including the x-amz-date header and the Authorization header, which
158−-- includes the signature.
159−--
160−-- The output is the list of headers to be added to authenticate the request.
161−signV4 :: SignParams -> NC.Request -> [Header]
111+signV4 :: SignParams -> NC.Request -> [(ByteString, ByteString)]
162signV4 !sp !req =
112signV4 !sp !req =
163− let scope = credentialScope sp
113+ let
164−
114+ region = fromMaybe "" $ spRegion sp
165− -- extra headers to be added for signing purposes.
115+ ts = spTimeStamp sp
166− extraHeaders =
116+ scope = mkScope ts region
167− ("X-Amz-Date", awsTimeFormatBS $ spTimeStamp sp)
117+ accessKey = toS $ spAccessKey sp
168− : ( -- payload hash is only used for S3 (not STS)
118+ secretKey = toS $ spSecretKey sp
119+ expiry = spExpirySecs sp
169− [ ( "x-amz-content-sha256",
120+ sha256Hdr = ("x-amz-content-sha256",
170− fromMaybe "UNSIGNED-PAYLOAD" $ spPayloadHash sp
121+ fromMaybe "UNSIGNED-PAYLOAD" $ spPayloadHash sp)
171− )
122+
172− | spService sp == ServiceS3
123+ -- headers to be added to the request
173− ]
124+ datePair = ("X-Amz-Date", awsTimeFormatBS ts)
174− )
125+ computedHeaders = NC.requestHeaders req ++
175− ++ maybeToList ((mk amzSecurityToken,) . BA.convert <$> spSessionToken sp)
126+ if isJust $ expiry
176−
127+ then []
128+ else map (\(x, y) -> (mk x, y)) [datePair, sha256Hdr]
129+ headersToSign = getHeadersToSign computedHeaders
130+ signedHeaderKeys = B.intercalate ";" $ sort $ map fst headersToSign
131+
132+ -- query-parameters to be added before signing for presigned URLs
133+ -- (i.e. when `isJust expiry`)
134+ authQP = [ ("X-Amz-Algorithm", "AWS4-HMAC-SHA256")
135+ , ("X-Amz-Credential", B.concat [accessKey, "/", scope])
136+ , datePair
137+ , ("X-Amz-Expires", maybe "" show expiry)
138+ , ("X-Amz-SignedHeaders", signedHeaderKeys)
139+ ]
140+ finalQP = parseQuery (NC.queryString req) ++
141+ if isJust expiry
142+ then (fmap . fmap) Just authQP
143+ else []
144+
177− -- 1. compute canonical request
145+ -- 1. compute canonical request
178− reqHeaders = NC.requestHeaders req ++ extraHeaders
146+ canonicalRequest = mkCanonicalRequest False sp
179− (canonicalRequest, signedHeaderKeys) =
147+ (NC.setQueryString finalQP req)
180− getCanonicalRequestAndSignedHeaders
148+ headersToSign
181− NotStreaming
149+
182− sp
183− req
184− reqHeaders
185−
186− -- 2. compute string to sign
150+ -- 2. compute string to sign
187− stringToSign = mkStringToSign (spTimeStamp sp) scope canonicalRequest
151+ stringToSign = mkStringToSign ts scope canonicalRequest
152+
188− -- 3.1 compute signing key
153+ -- 3.1 compute signing key
189− signingKey = getSigningKey sp
154+ signingKey = mkSigningKey ts region secretKey
155+
190− -- 3.2 compute signature
156+ -- 3.2 compute signature
191− signature = computeSignature stringToSign signingKey
157+ signature = computeSignature stringToSign signingKey
158+
192− -- 4. compute auth header
159+ -- 4. compute auth header
193− authHeader = mkAuthHeader (spAccessKey sp) scope signedHeaderKeys signature
160+ authHeader = mkAuthHeader (spAccessKey sp) scope signedHeaderKeys signature
194− in authHeader : extraHeaders
161+
195−
162+ -- finally compute output pairs
196−credentialScope :: SignParams -> ByteString
163+ output = if isJust expiry
197−credentialScope sp =
164+ then ("X-Amz-Signature", signature) : authQP
198− let region = fromMaybe "" $ spRegion sp
165+ else [(\(x, y) -> (CI.foldedCase x, y)) authHeader,
166+ datePair, sha256Hdr]
167+
168+ in output
169+
170+
171+mkScope :: UTCTime -> Text -> ByteString
199− in B.intercalate
172+mkScope ts region = B.intercalate "/"
200− "/"
201− [ encodeUtf8 $ Time.formatTime Time.defaultTimeLocale "%Y%m%d" $ spTimeStamp sp,
173+ [ toS $ Time.formatTime Time.defaultTimeLocale "%Y%m%d" ts
202− encodeUtf8 region,
174+ , toS region
203− toByteString $ spService sp,
175+ , "s3"
204− "aws4_request"
176+ , "aws4_request"
205− ]
177+ ]
206
178
207−-- Folds header name, trims whitespace in header values, skips ignored headers
208−-- and sorts headers.
209getHeadersToSign :: [Header] -> [(ByteString, ByteString)]
179getHeadersToSign :: [Header] -> [(ByteString, ByteString)]
210getHeadersToSign !h =
180getHeadersToSign !h =
211 filter ((\hdr -> not $ Set.member hdr ignoredHeaders) . fst) $
181 filter ((\hdr -> not $ Set.member hdr ignoredHeaders) . fst) $
212− map (bimap CI.foldedCase stripBS) h
182+ map (\(x, y) -> (CI.foldedCase x, stripBS y)) h
213−
183+
214−-- | Given the list of headers in the request, computes the canonical headers
215−-- and the signed headers strings.
216−getCanonicalHeaders :: NonEmpty Header -> (ByteString, ByteString)
217−getCanonicalHeaders h =
218− let -- Folds header name, trims spaces in header values, skips ignored
219− -- headers and sorts headers by name (we must not re-order multi-valued
220− -- headers).
221− headersToSign =
222− NE.toList $
223− NE.sortBy (\a b -> compare (fst a) (fst b)) $
224− NE.fromList $
225− NE.filter ((\hdr -> not $ Set.member hdr ignoredHeaders) . fst) $
226− NE.map (bimap CI.foldedCase stripBS) h
227−
228− canonicalHeaders = mconcat $ map (\(a, b) -> a <> ":" <> b <> "\n") headersToSign
229− signedHeaderKeys = B.intercalate ";" $ map fst headersToSign
230− in (canonicalHeaders, signedHeaderKeys)
231−
232−getCanonicalRequestAndSignedHeaders ::
233− IsStreaming ->
234− SignParams ->
235− NC.Request ->
236− [Header] ->
237− (ByteString, ByteString)
238−getCanonicalRequestAndSignedHeaders isStreaming sp req requestHeaders =
239− let httpMethod = NC.method req
240−
241− canonicalUri = uriEncode False $ NC.path req
242−
243− canonicalQueryString =
244− B.intercalate "&" $
245− map (\(x, y) -> B.concat [x, "=", y]) $
246− sort $
247− map
248− ( bimap (uriEncode True) (maybe "" (uriEncode True))
249− )
250− (parseQuery $ NC.queryString req)
251−
252− (canonicalHeaders, signedHeaderKeys) = getCanonicalHeaders $ NE.fromList requestHeaders
253− payloadHashStr =
254− case isStreaming of
255− IsStreamingLength _ -> "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
256− NotStreaming -> fromMaybe "UNSIGNED-PAYLOAD" $ spPayloadHash sp
257−
258− canonicalRequest =
259− B.intercalate
260− "\n"
261− [ httpMethod,
262− canonicalUri,
263− canonicalQueryString,
264− canonicalHeaders,
265− signedHeaderKeys,
266− payloadHashStr
267− ]
268− in (canonicalRequest, signedHeaderKeys)
269−
270−mkCanonicalRequest ::
271− Bool ->
272− SignParams ->
273− NC.Request ->
274− [(ByteString, ByteString)] ->
184+mkCanonicalRequest :: Bool -> SignParams -> NC.Request -> [(ByteString, ByteString)]
275− ByteString
185+ -> ByteString
276mkCanonicalRequest !isStreaming !sp !req !headersForSign =
186mkCanonicalRequest !isStreaming !sp !req !headersForSign =
277− let httpMethod = NC.method req
187+ let
278− canonicalUri = uriEncode False $ NC.path req
279− canonicalQueryString =
280− B.intercalate "&" $
188+ canonicalQueryString = B.intercalate "&" $
281− map (\(x, y) -> B.concat [x, "=", y]) $
189+ map (\(x, y) -> B.concat [x, "=", y]) $
282− sortBy (\a b -> compare (fst a) (fst b)) $
190+ sort $ map (\(x, y) ->
283− map
284− ( bimap (uriEncode True) (maybe "" (uriEncode True))
191+ (uriEncode True x, maybe "" (uriEncode True) y)) $
285− )
286− (parseQuery $ NC.queryString req)
192+ (parseQuery $ NC.queryString req)
287− sortedHeaders = sort headersForSign
288− canonicalHeaders =
289− B.concat $
290− map (\(x, y) -> B.concat [x, ":", y, "\n"]) sortedHeaders
291− signedHeaders = B.intercalate ";" $ map fst sortedHeaders
292− payloadHashStr =
293− if isStreaming
294− then "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
295− else fromMaybe "UNSIGNED-PAYLOAD" $ spPayloadHash sp
296− in B.intercalate
297− "\n"
298− [ httpMethod,
299− canonicalUri,
300− canonicalQueryString,
301− canonicalHeaders,
302− signedHeaders,
303− payloadHashStr
304− ]
305
193
306−mkStringToSign :: UTCTime -> ByteString -> ByteString -> ByteString
194+ sortedHeaders = sort headersForSign
307−mkStringToSign ts !scope !canonicalRequest =
195+
196+ canonicalHeaders = B.concat $
197+ map (\(x, y) -> B.concat [x, ":", y, "\n"]) sortedHeaders
198+
308− B.intercalate
199+ signedHeaders = B.intercalate ";" $ map fst sortedHeaders
309− "\n"
200+
201+ payloadHashStr =
202+ if isStreaming
310− [ "AWS4-HMAC-SHA256",
203+ then "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
311− awsTimeFormatBS ts,
204+ else fromMaybe "UNSIGNED-PAYLOAD" $ spPayloadHash sp
312− scope,
205+ in
313− hashSHA256 canonicalRequest
206+ B.intercalate "\n"
207+ [ NC.method req
208+ , uriEncode False $ NC.path req
209+ , canonicalQueryString
210+ , canonicalHeaders
211+ , signedHeaders
212+ , payloadHashStr
314 ]
213 ]
315
214
316−getSigningKey :: SignParams -> ByteString
215+mkStringToSign :: UTCTime -> ByteString -> ByteString -> ByteString
317−getSigningKey sp =
216+mkStringToSign ts !scope !canonicalRequest = B.intercalate "\n"
217+ [ "AWS4-HMAC-SHA256"
218+ , awsTimeFormatBS ts
219+ , scope
220+ , hashSHA256 canonicalRequest
221+ ]
222+
223+mkSigningKey :: UTCTime -> Text -> ByteString -> ByteString
318− hmacSHA256RawBS "aws4_request"
224+mkSigningKey ts region !secretKey = hmacSHA256RawBS "aws4_request"
319− . hmacSHA256RawBS (toByteString $ spService sp)
225+ . hmacSHA256RawBS "s3"
320− . hmacSHA256RawBS (encodeUtf8 $ fromMaybe "" $ spRegion sp)
226+ . hmacSHA256RawBS (toS region)
321− . hmacSHA256RawBS (awsDateFormatBS $ spTimeStamp sp)
227+ . hmacSHA256RawBS (awsDateFormatBS ts)
322− $ B.concat ["AWS4", BA.convert $ spSecretKey sp]
228+ $ B.concat ["AWS4", secretKey]
323
229
324computeSignature :: ByteString -> ByteString -> ByteString
230computeSignature :: ByteString -> ByteString -> ByteString
325computeSignature !toSign !key = digestToBase16 $ hmacSHA256 toSign key
231computeSignature !toSign !key = digestToBase16 $ hmacSHA256 toSign key
@@ -327,168 +233,159 @@ computeSignature !toSign !key = digestToBase16 $ hmacSHA256 toSign key
327-- | Takes a validated Post Policy JSON bytestring, the signing time,
233-- | Takes a validated Post Policy JSON bytestring, the signing time,
328-- and ConnInfo and returns form-data for the POST upload containing
234-- and ConnInfo and returns form-data for the POST upload containing
329-- just the signature and the encoded post-policy.
235-- just the signature and the encoded post-policy.
330−signV4PostPolicy ::
236+signV4PostPolicy :: ByteString -> SignParams
331− ByteString ->
332− SignParams ->
333− Map.HashMap Text ByteString
237+ -> Map.HashMap Text ByteString
334signV4PostPolicy !postPolicyJSON !sp =
238signV4PostPolicy !postPolicyJSON !sp =
239+ let
335− let stringToSign = Base64.encode postPolicyJSON
240+ stringToSign = Base64.encode postPolicyJSON
336− signingKey = getSigningKey sp
241+ region = fromMaybe "" $ spRegion sp
242+ signingKey = mkSigningKey (spTimeStamp sp) region $ toS $ spSecretKey sp
337− signature = computeSignature stringToSign signingKey
243+ signature = computeSignature stringToSign signingKey
338− in Map.fromList $
244+ in
339− [ ("x-amz-signature", signature),
245+ Map.fromList [ ("x-amz-signature", signature)
340− ("policy", stringToSign)
246+ , ("policy", stringToSign)
341− ]
247+ ]
342− ++ maybeToList ((decodeUtf8 amzSecurityToken,) . BA.convert <$> spSessionToken sp)
343
248
344chunkSizeConstant :: Int
249chunkSizeConstant :: Int
345chunkSizeConstant = 64 * 1024
250chunkSizeConstant = 64 * 1024
346
251
347-- base16Len computes the number of bytes required to represent @n (> 0)@ in
252-- base16Len computes the number of bytes required to represent @n (> 0)@ in
348-- hexadecimal.
253-- hexadecimal.
349−base16Len :: (Integral a) => a -> Int
254+base16Len :: Integral a => a -> Int
350−base16Len n
255+base16Len n | n == 0 = 0
351− | n == 0 = 0
352− | otherwise = 1 + base16Len (n `div` 16)
256+ | otherwise = 1 + base16Len (n `div` 16)
353
257
354signedStreamLength :: Int64 -> Int64
258signedStreamLength :: Int64 -> Int64
355signedStreamLength dataLen =
259signedStreamLength dataLen =
260+ let
356− let chunkSzInt = fromIntegral chunkSizeConstant
261+ chunkSzInt = fromIntegral chunkSizeConstant
357− (numChunks, lastChunkLen) = quotRem dataLen chunkSzInt
262+ (numChunks, lastChunkLen) = quotRem dataLen chunkSzInt
263+
264+
358− -- Structure of a chunk:
265+ -- Structure of a chunk:
359− -- string(IntHexBase(chunk-size)) + ";chunk-signature=" + signature + \r\n + chunk-data + \r\n
266+ -- string(IntHexBase(chunk-size)) + ";chunk-signature=" + signature + \r\n + chunk-data + \r\n
360− encodedChunkLen csz = fromIntegral (base16Len csz) + 17 + 64 + 2 + csz + 2
267+ encodedChunkLen csz = fromIntegral (base16Len csz) + 17 + 64 + 2 + csz + 2
361− fullChunkSize = encodedChunkLen chunkSzInt
268+ fullChunkSize = encodedChunkLen chunkSzInt
362− lastChunkSize = bool 0 (encodedChunkLen lastChunkLen) $ lastChunkLen > 0
269+ lastChunkSize = bool 0 (encodedChunkLen lastChunkLen) $ lastChunkLen > 0
363− finalChunkSize = 1 + 17 + 64 + 2 + 2
270+ finalChunkSize = 1 + 17 + 64 + 2 + 2
271+ in
364− in numChunks * fullChunkSize + lastChunkSize + finalChunkSize
272+ numChunks * fullChunkSize + lastChunkSize + finalChunkSize
365−
273+
366−-- For streaming S3, we need to update the content-encoding header.
367−addContentEncoding :: [Header] -> [Header]
368−addContentEncoding hs =
369− -- assume there is at most one content-encoding header.
370− let (ceHdrs, others) = partition ((== hContentEncoding) . fst) hs
371− in maybe
372− (hContentEncoding, "aws-chunked")
373− (\(k, v) -> (k, v <> ",aws-chunked"))
374− (listToMaybe ceHdrs)
375− : others
376−
377−signV4Stream ::
274+signV4Stream :: Int64 -> SignParams -> NC.Request
378− Int64 ->
275+ -> (C.ConduitT () ByteString (C.ResourceT IO) () -> NC.Request)
379− SignParams ->
380− NC.Request ->
381− (C.ConduitT () ByteString (C.ResourceT IO) () -> NC.Request)
276+ -- -> ([Header], C.ConduitT () ByteString (C.ResourceT IO) () -> NC.RequestBody)
382signV4Stream !payloadLength !sp !req =
277signV4Stream !payloadLength !sp !req =
278+ let
383− let ts = spTimeStamp sp
279+ ts = spTimeStamp sp
384−
280+
385− -- compute the updated list of headers to be added for signing purposes.
281+ addContentEncoding hs =
386− signedContentLength = signedStreamLength payloadLength
282+ let ceMay = headMay $ filter (\(x, _) -> x == "content-encoding") hs
387− extraHeaders =
283+ in case ceMay of
284+ Nothing -> ("content-encoding", "aws-chunked") : hs
285+ Just (_, ce) -> ("content-encoding", ce <> ",aws-chunked") :
286+ filter (\(x, _) -> x /= "content-encoding") hs
287+
288+ -- headers to be added to the request
388− [ ("X-Amz-Date", awsTimeFormatBS $ spTimeStamp sp),
289+ datePair = ("X-Amz-Date", awsTimeFormatBS ts)
290+ computedHeaders = addContentEncoding $
291+ datePair : NC.requestHeaders req
292+
293+ -- headers specific to streaming signature
294+ signedContentLength = signedStreamLength payloadLength
295+ streamingHeaders :: [Header]
296+ streamingHeaders =
389− ("x-amz-decoded-content-length", showBS payloadLength),
297+ [ ("x-amz-decoded-content-length", show payloadLength)
390− ("content-length", showBS signedContentLength),
298+ , ("content-length", show signedContentLength )
391− ("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD")
299+ , ("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD")
300+ ]
301+ headersToSign = getHeadersToSign $ computedHeaders ++ streamingHeaders
302+ signedHeaderKeys = B.intercalate ";" $ sort $ map fst headersToSign
303+ finalQP = parseQuery (NC.queryString req)
304+
305+ -- 1. Compute Seed Signature
306+ -- 1.1 Canonical Request
307+ canonicalReq = mkCanonicalRequest True sp
308+ (NC.setQueryString finalQP req)
309+ headersToSign
310+
311+ region = fromMaybe "" $ spRegion sp
312+ scope = mkScope ts region
313+ accessKey = spAccessKey sp
314+ secretKey = spSecretKey sp
315+
316+ -- 1.2 String toSign
317+ stringToSign = mkStringToSign ts scope canonicalReq
318+
319+ -- 1.3 Compute signature
320+ -- 1.3.1 compute signing key
321+ signingKey = mkSigningKey ts region $ toS secretKey
322+
323+ -- 1.3.2 Compute signature
324+ seedSignature = computeSignature stringToSign signingKey
325+
326+ -- 1.3.3 Compute Auth Header
327+ authHeader = mkAuthHeader accessKey scope signedHeaderKeys seedSignature
328+
329+ -- 1.4 Updated headers for the request
330+ finalReqHeaders = authHeader : (computedHeaders ++ streamingHeaders)
331+ -- headersToAdd = authHeader : datePair : streamingHeaders
332+
333+ toHexStr n = B8.pack $ printf "%x" n
334+
335+ (numParts, lastPSize) = payloadLength `quotRem` fromIntegral chunkSizeConstant
336+
337+ -- Function to compute string to sign for each chunk.
338+ chunkStrToSign prevSign currChunkHash =
339+ B.intercalate "\n"
340+ [ "AWS4-HMAC-SHA256-PAYLOAD"
341+ , awsTimeFormatBS ts
342+ , scope
343+ , prevSign
344+ , hashSHA256 ""
345+ , currChunkHash
392 ]
346 ]
393− ++ maybeToList ((mk amzSecurityToken,) . BA.convert <$> spSessionToken sp)
347+
394− requestHeaders =
395− addContentEncoding $
396− foldr setHeader (NC.requestHeaders req) extraHeaders
397−
398− -- 1. Compute Seed Signature
399− -- 1.1 Canonical Request
400− (canonicalReq, signedHeaderKeys) =
401− getCanonicalRequestAndSignedHeaders
402− (IsStreamingLength payloadLength)
403− sp
404− req
405− requestHeaders
406−
407− scope = credentialScope sp
408− accessKey = spAccessKey sp
409− -- 1.2 String toSign
410− stringToSign = mkStringToSign ts scope canonicalReq
411− -- 1.3 Compute signature
412− -- 1.3.1 compute signing key
413− signingKey = getSigningKey sp
414− -- 1.3.2 Compute signature
415− seedSignature = computeSignature stringToSign signingKey
416− -- 1.3.3 Compute Auth Header
417− authHeader = mkAuthHeader accessKey scope signedHeaderKeys seedSignature
418− -- 1.4 Updated headers for the request
419− finalReqHeaders = authHeader : requestHeaders
420− -- headersToAdd = authHeader : datePair : streamingHeaders
421−
422− toHexStr n = B8.pack $ printf "%x" n
423− (numParts, lastPSize) = payloadLength `quotRem` fromIntegral chunkSizeConstant
424− -- Function to compute string to sign for each chunk.
425− chunkStrToSign prevSign currChunkHash =
426− B.intercalate
427− "\n"
428− [ "AWS4-HMAC-SHA256-PAYLOAD",
429− awsTimeFormatBS ts,
430− scope,
431− prevSign,
432− hashSHA256 "",
433− currChunkHash
434− ]
435− -- Read n byte from upstream and return a strict bytestring.
348+ -- Read n byte from upstream and return a strict bytestring.
436− mustTakeN n = do
349+ mustTakeN n = do
437− bs <- LB.toStrict <$> (C.takeCE n C..| C.sinkLazy)
350+ bs <- toS <$> (C.takeCE n C..| C.sinkLazy)
438 when (B.length bs /= n) $
351 when (B.length bs /= n) $
439− throwIO MErrVStreamingBodyUnexpectedEOF
352+ throwIO MErrVStreamingBodyUnexpectedEOF
440 return bs
353 return bs
354+
441− signerConduit n lps prevSign =
355+ signerConduit n lps prevSign =
442− -- First case encodes a full chunk of length
356+ -- First case encodes a full chunk of length
443− -- 'chunkSizeConstant'.
357+ -- 'chunkSizeConstant'.
444− if
445− | n > 0 -> do
358+ if | n > 0 -> do
446− bs <- mustTakeN chunkSizeConstant
359+ bs <- mustTakeN chunkSizeConstant
447− let strToSign = chunkStrToSign prevSign (hashSHA256 bs)
360+ let strToSign = chunkStrToSign prevSign (hashSHA256 bs)
448− nextSign = computeSignature strToSign signingKey
361+ nextSign = computeSignature strToSign signingKey
449− chunkBS =
450− toHexStr chunkSizeConstant
362+ chunkBS = toHexStr chunkSizeConstant
451− <> ";chunk-signature="
363+ <> ";chunk-signature="
452− <> nextSign
364+ <> nextSign <> "\r\n" <> bs <> "\r\n"
453− <> "\r\n"
454− <> bs
455− <> "\r\n"
456− C.yield chunkBS
365+ C.yield chunkBS
457− signerConduit (n - 1) lps nextSign
366+ signerConduit (n-1) lps nextSign
458−
367+
459− -- Second case encodes the last chunk which is smaller than
368+ -- Second case encodes the last chunk which is smaller than
460− -- 'chunkSizeConstant'
369+ -- 'chunkSizeConstant'
461− | lps > 0 -> do
370+ | lps > 0 -> do
462− bs <- mustTakeN $ fromIntegral lps
371+ bs <- mustTakeN $ fromIntegral lps
463− let strToSign = chunkStrToSign prevSign (hashSHA256 bs)
372+ let strToSign = chunkStrToSign prevSign (hashSHA256 bs)
464− nextSign = computeSignature strToSign signingKey
373+ nextSign = computeSignature strToSign signingKey
465− chunkBS =
466− toHexStr lps
467− <> ";chunk-signature="
374+ chunkBS = toHexStr lps <> ";chunk-signature="
468− <> nextSign
375+ <> nextSign <> "\r\n" <> bs <> "\r\n"
469− <> "\r\n"
470− <> bs
471− <> "\r\n"
472− C.yield chunkBS
376+ C.yield chunkBS
473− signerConduit 0 0 nextSign
377+ signerConduit 0 0 nextSign
474−
378+
475− -- Last case encodes the final signature chunk that has no
379+ -- Last case encodes the final signature chunk that has no
476− -- data.
380+ -- data.
477− | otherwise -> do
381+ | otherwise -> do
478− let strToSign = chunkStrToSign prevSign (hashSHA256 "")
382+ let strToSign = chunkStrToSign prevSign (hashSHA256 "")
479− nextSign = computeSignature strToSign signingKey
383+ nextSign = computeSignature strToSign signingKey
480− lastChunkBS = "0;chunk-signature=" <> nextSign <> "\r\n\r\n"
384+ lastChunkBS = "0;chunk-signature=" <> nextSign <> "\r\n\r\n"
481− C.yield lastChunkBS
385+ C.yield lastChunkBS
482− in \src ->
386+ in
483− req
484− { NC.requestHeaders = finalReqHeaders,
387+ \src -> req { NC.requestHeaders = finalReqHeaders
485− NC.requestBody =
388+ , NC.requestBody =
486− NC.requestBodySource signedContentLength $
389+ NC.requestBodySource signedContentLength $
487− src C..| signerConduit numParts lastPSize seedSignature
390+ src C..| signerConduit numParts lastPSize seedSignature
488− }
391+ }
489−
490−-- "setHeader r hdr" adds the hdr to r, replacing it in r if it already exists.
491−setHeader :: Header -> RequestHeaders -> RequestHeaders
492−setHeader hdr r =
493− let r' = filter (\(name, _) -> name /= fst hdr) r
494− in hdr : r'
src/Network/Minio/Utils.hs+127−140
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -16,77 +16,70 @@
16
16
17module Network.Minio.Utils where
17module Network.Minio.Utils where
18
18
19−import qualified Conduit as C
19+import qualified Conduit as C
20−import Control.Monad.IO.Unlift (MonadUnliftIO)
20+import Control.Monad.IO.Unlift (MonadUnliftIO)
21−import qualified Control.Monad.Trans.Resource as R
21+import qualified Control.Monad.Trans.Resource as R
22−import qualified Data.ByteString as B
22+import qualified Data.ByteString as B
23−import qualified Data.ByteString.Lazy as LB
23+import qualified Data.ByteString.Lazy as LB
24−import Data.CaseInsensitive (mk, original)
24+import Data.CaseInsensitive (mk, original)
25−import qualified Data.Conduit.Binary as CB
25+import qualified Data.Conduit.Binary as CB
26−import qualified Data.HashMap.Strict as H
26+import qualified Data.HashMap.Strict as H
27−import qualified Data.Text as T
27+import qualified Data.List as List
28+import qualified Data.Text as T
28−import Data.Text.Read (decimal)
29+import Data.Text.Read (decimal)
29−import Data.Time
30− ( defaultTimeLocale,
30+import Data.Time (defaultTimeLocale, parseTimeM,
31− parseTimeM,
32− rfc822DateFormat,
31+ rfc822DateFormat)
33− )
34−import Lib.Prelude
35−import Network.HTTP.Conduit (Response)
32+import Network.HTTP.Conduit (Response)
36−import qualified Network.HTTP.Conduit as NC
33+import qualified Network.HTTP.Conduit as NC
37−import qualified Network.HTTP.Types as HT
34+import qualified Network.HTTP.Types as HT
38−import qualified Network.HTTP.Types.Header as Hdr
35+import qualified Network.HTTP.Types.Header as Hdr
39−import Network.Minio.Data.ByteString
40−import Network.Minio.JsonParser (parseErrResponseJSON)
41−import Network.Minio.XmlCommon (parseErrResponse)
42−import qualified System.IO as IO
36+import qualified System.IO as IO
43−import qualified UnliftIO as U
37+import qualified UnliftIO as U
44−import qualified UnliftIO.Async as A
38+import qualified UnliftIO.Async as A
45−
39+import qualified UnliftIO.MVar as UM
46−allocateReadFile ::
40+
41+import Lib.Prelude
42+
43+import Network.Minio.Data
44+import Network.Minio.Data.ByteString
45+import Network.Minio.JsonParser (parseErrResponseJSON)
46+import Network.Minio.XmlParser (parseErrResponse)
47+
47− (MonadUnliftIO m, R.MonadResource m) =>
48+allocateReadFile :: (MonadUnliftIO m, R.MonadResource m)
48− FilePath ->
49− m (R.ReleaseKey, Handle)
49+ => FilePath -> m (R.ReleaseKey, Handle)
50allocateReadFile fp = do
50allocateReadFile fp = do
51 (rk, hdlE) <- R.allocate (openReadFile fp) cleanup
51 (rk, hdlE) <- R.allocate (openReadFile fp) cleanup
52− either (\(e :: U.IOException) -> throwIO e) (return . (rk,)) hdlE
52+ either (\(e :: IOException) -> throwIO e) (return . (rk,)) hdlE
53 where
53 where
54 openReadFile f = U.try $ IO.openBinaryFile f IO.ReadMode
54 openReadFile f = U.try $ IO.openBinaryFile f IO.ReadMode
55 cleanup = either (const $ return ()) IO.hClose
55 cleanup = either (const $ return ()) IO.hClose
56
56
57-- | Queries the file size from the handle. Catches any file operation
57-- | Queries the file size from the handle. Catches any file operation
58-- exceptions and returns Nothing instead.
58-- exceptions and returns Nothing instead.
59−getFileSize ::
60− (MonadUnliftIO m) =>
59+getFileSize :: (MonadUnliftIO m, R.MonadResource m)
61− Handle ->
62− m (Maybe Int64)
60+ => Handle -> m (Maybe Int64)
63getFileSize h = do
61getFileSize h = do
64 resE <- liftIO $ try $ fromIntegral <$> IO.hFileSize h
62 resE <- liftIO $ try $ fromIntegral <$> IO.hFileSize h
65 case resE of
63 case resE of
66− Left (_ :: U.IOException) -> return Nothing
64+ Left (_ :: IOException) -> return Nothing
67− Right s -> return $ Just s
65+ Right s -> return $ Just s
68
66
69-- | Queries if handle is seekable. Catches any file operation
67-- | Queries if handle is seekable. Catches any file operation
70-- exceptions and return False instead.
68-- exceptions and return False instead.
71−isHandleSeekable ::
69+isHandleSeekable :: (R.MonadResource m, MonadUnliftIO m)
72− (R.MonadResource m) =>
73− Handle ->
70+ => Handle -> m Bool
74− m Bool
75isHandleSeekable h = do
71isHandleSeekable h = do
76 resE <- liftIO $ try $ IO.hIsSeekable h
72 resE <- liftIO $ try $ IO.hIsSeekable h
77 case resE of
73 case resE of
78− Left (_ :: U.IOException) -> return False
74+ Left (_ :: IOException) -> return False
79− Right v -> return v
75+ Right v -> return v
80
76
81-- | Helper function that opens a handle to the filepath and performs
77-- | Helper function that opens a handle to the filepath and performs
82-- the given action on it. Exceptions of type MError are caught and
78-- the given action on it. Exceptions of type MError are caught and
83-- returned - both during file handle allocation and when the action
79-- returned - both during file handle allocation and when the action
84-- is run.
80-- is run.
85−withNewHandle ::
86− (MonadUnliftIO m, R.MonadResource m) =>
81+withNewHandle :: (MonadUnliftIO m, R.MonadResource m)
87− FilePath ->
88− (Handle -> m a) ->
89− m (Either U.IOException a)
82+ => FilePath -> (Handle -> m a) -> m (Either IOException a)
90withNewHandle fp fileAction = do
83withNewHandle fp fileAction = do
91 -- opening a handle can throw MError exception.
84 -- opening a handle can throw MError exception.
92 handleE <- try $ allocateReadFile fp
85 handleE <- try $ allocateReadFile fp
@@ -100,61 +93,34 @@ withNewHandle fp fileAction = do
100 return resE
93 return resE
101
94
102mkHeaderFromPairs :: [(ByteString, ByteString)] -> [HT.Header]
95mkHeaderFromPairs :: [(ByteString, ByteString)] -> [HT.Header]
103−mkHeaderFromPairs = map (first mk)
96+mkHeaderFromPairs = map ((\(x, y) -> (mk x, y)))
104
97
105lookupHeader :: HT.HeaderName -> [HT.Header] -> Maybe ByteString
98lookupHeader :: HT.HeaderName -> [HT.Header] -> Maybe ByteString
106−lookupHeader hdr = listToMaybe . map snd . filter (\(h, _) -> h == hdr)
99+lookupHeader hdr = headMay . map snd . filter (\(h, _) -> h == hdr)
107
100
108getETagHeader :: [HT.Header] -> Maybe Text
101getETagHeader :: [HT.Header] -> Maybe Text
109getETagHeader hs = decodeUtf8Lenient <$> lookupHeader Hdr.hETag hs
102getETagHeader hs = decodeUtf8Lenient <$> lookupHeader Hdr.hETag hs
110
103
111getMetadata :: [HT.Header] -> [(Text, Text)]
104getMetadata :: [HT.Header] -> [(Text, Text)]
112getMetadata =
105getMetadata =
113− map (\(x, y) -> (decodeUtf8Lenient $ original x, decodeUtf8Lenient $ stripBS y))
106+ map ((\(x, y) -> (decodeUtf8Lenient $ original x, decodeUtf8Lenient $ stripBS y)))
114−
115−-- | If the given header name has the @X-Amz-Meta-@ prefix, it is
116−-- stripped and a Just is returned.
117−userMetadataHeaderNameMaybe :: Text -> Maybe Text
118−userMetadataHeaderNameMaybe k =
119− let prefix = T.toCaseFold "X-Amz-Meta-"
120− n = T.length prefix
121− in if T.toCaseFold (T.take n k) == prefix
122− then Just (T.drop n k)
123− else Nothing
124
107
125toMaybeMetadataHeader :: (Text, Text) -> Maybe (Text, Text)
108toMaybeMetadataHeader :: (Text, Text) -> Maybe (Text, Text)
126toMaybeMetadataHeader (k, v) =
109toMaybeMetadataHeader (k, v) =
127− (,v) <$> userMetadataHeaderNameMaybe k
110+ (, v) <$> userMetadataHeaderNameMaybe k
128
111
129getNonUserMetadataMap :: [(Text, Text)] -> H.HashMap Text Text
112getNonUserMetadataMap :: [(Text, Text)] -> H.HashMap Text Text
130−getNonUserMetadataMap =
113+getNonUserMetadataMap = H.fromList
131− H.fromList
132− . filter
133− ( isNothing
114+ . filter ( isNothing
134− . userMetadataHeaderNameMaybe
115+ . userMetadataHeaderNameMaybe
135− . fst
116+ . fst
136− )
117+ )
137−
138−addXAmzMetaPrefix :: Text -> Text
139−addXAmzMetaPrefix s
140− | isJust (userMetadataHeaderNameMaybe s) = s
141− | otherwise = "X-Amz-Meta-" <> s
142−
143−mkHeaderFromMetadata :: [(Text, Text)] -> [HT.Header]
144−mkHeaderFromMetadata = map (\(x, y) -> (mk $ encodeUtf8 $ addXAmzMetaPrefix x, encodeUtf8 y))
145
118
146-- | This function collects all headers starting with `x-amz-meta-`
119-- | This function collects all headers starting with `x-amz-meta-`
147-- and strips off this prefix, and returns a map.
120-- and strips off this prefix, and returns a map.
148getUserMetadataMap :: [(Text, Text)] -> H.HashMap Text Text
121getUserMetadataMap :: [(Text, Text)] -> H.HashMap Text Text
149−getUserMetadataMap =
122+getUserMetadataMap = H.fromList
150− H.fromList
151− . mapMaybe toMaybeMetadataHeader
123+ . mapMaybe toMaybeMetadataHeader
152−
153−getHostHeader :: (ByteString, Int) -> ByteString
154−getHostHeader (host_, port_) =
155− if port_ == 80 || port_ == 443
156− then host_
157− else host_ <> ":" <> show port_
158
124
159getLastModifiedHeader :: [HT.Header] -> Maybe UTCTime
125getLastModifiedHeader :: [HT.Header] -> Maybe UTCTime
160getLastModifiedHeader hs = do
126getLastModifiedHeader hs = do
@@ -164,21 +130,19 @@ getLastModifiedHeader hs = do
164getContentLength :: [HT.Header] -> Maybe Int64
130getContentLength :: [HT.Header] -> Maybe Int64
165getContentLength hs = do
131getContentLength hs = do
166 nbs <- decodeUtf8Lenient <$> lookupHeader Hdr.hContentLength hs
132 nbs <- decodeUtf8Lenient <$> lookupHeader Hdr.hContentLength hs
167− fst <$> either (const Nothing) Just (decimal nbs)
133+ fst <$> hush (decimal nbs)
134+
168
135
169decodeUtf8Lenient :: ByteString -> Text
136decodeUtf8Lenient :: ByteString -> Text
170decodeUtf8Lenient = decodeUtf8With lenientDecode
137decodeUtf8Lenient = decodeUtf8With lenientDecode
171
138
172isSuccessStatus :: HT.Status -> Bool
139isSuccessStatus :: HT.Status -> Bool
173−isSuccessStatus sts =
174− let s = HT.statusCode sts
140+isSuccessStatus sts = let s = HT.statusCode sts
175− in (s >= 200 && s < 300)
141+ in (s >= 200 && s < 300)
176−
142+
177−httpLbs ::
178− (MonadIO m) =>
143+httpLbs :: MonadIO m
179− NC.Request ->
144+ => NC.Request -> NC.Manager
180− NC.Manager ->
181− m (NC.Response LByteString)
145+ -> m (NC.Response LByteString)
182httpLbs req mgr = do
146httpLbs req mgr = do
183 respE <- liftIO $ tryHttpEx $ NC.httpLbs req mgr
147 respE <- liftIO $ tryHttpEx $ NC.httpLbs req mgr
184 resp <- either throwIO return respE
148 resp <- either throwIO return respE
@@ -190,26 +154,21 @@ httpLbs req mgr = do
190 Just "application/json" -> do
154 Just "application/json" -> do
191 sErr <- parseErrResponseJSON $ NC.responseBody resp
155 sErr <- parseErrResponseJSON $ NC.responseBody resp
192 throwIO sErr
156 throwIO sErr
193− _ ->
157+
194− throwIO $
195− NC.HttpExceptionRequest req $
158+ _ -> throwIO $ NC.HttpExceptionRequest req $
196− NC.StatusCodeException (void resp) (showBS resp)
159+ NC.StatusCodeException (void resp) (show resp)
197
160
198 return resp
161 return resp
199 where
162 where
200− tryHttpEx ::
201− IO (NC.Response LByteString) ->
163+ tryHttpEx :: IO (NC.Response LByteString)
202− IO (Either NC.HttpException (NC.Response LByteString))
164+ -> IO (Either NC.HttpException (NC.Response LByteString))
203 tryHttpEx = try
165 tryHttpEx = try
204− contentTypeMay resp =
205− lookupHeader Hdr.hContentType $
166+ contentTypeMay resp = lookupHeader Hdr.hContentType $
206− NC.responseHeaders resp
167+ NC.responseHeaders resp
207−
168+
208−http ::
209− (MonadUnliftIO m, R.MonadResource m) =>
169+http :: (MonadUnliftIO m, R.MonadResource m)
210− NC.Request ->
170+ => NC.Request -> NC.Manager
211− NC.Manager ->
212− m (Response (C.ConduitT () ByteString m ()))
171+ -> m (Response (C.ConduitT () ByteString m ()))
213http req mgr = do
172http req mgr = do
214 respE <- tryHttpEx $ NC.http req mgr
173 respE <- tryHttpEx $ NC.http req mgr
215 resp <- either throwIO return respE
174 resp <- either throwIO return respE
@@ -219,31 +178,25 @@ http req mgr = do
219 respBody <- C.connect (NC.responseBody resp) CB.sinkLbs
178 respBody <- C.connect (NC.responseBody resp) CB.sinkLbs
220 sErr <- parseErrResponse respBody
179 sErr <- parseErrResponse respBody
221 throwIO sErr
180 throwIO sErr
181+
222 _ -> do
182 _ -> do
223 content <- LB.toStrict . NC.responseBody <$> NC.lbsResponse resp
183 content <- LB.toStrict . NC.responseBody <$> NC.lbsResponse resp
224− throwIO $
225− NC.HttpExceptionRequest req $
184+ throwIO $ NC.HttpExceptionRequest req $
226− NC.StatusCodeException (void resp) content
185+ NC.StatusCodeException (void resp) content
186+
227
187
228 return resp
188 return resp
229 where
189 where
230− tryHttpEx ::
231− (MonadUnliftIO m) =>
190+ tryHttpEx :: (MonadUnliftIO m) => m a
232− m a ->
233− m (Either NC.HttpException a)
191+ -> m (Either NC.HttpException a)
234 tryHttpEx = try
192 tryHttpEx = try
235− contentTypeMay resp =
236− lookupHeader Hdr.hContentType $
193+ contentTypeMay resp = lookupHeader Hdr.hContentType $
237− NC.responseHeaders resp
194+ NC.responseHeaders resp
238
195
239-- Similar to mapConcurrently but limits the number of threads that
196-- Similar to mapConcurrently but limits the number of threads that
240-- can run using a quantity semaphore.
197-- can run using a quantity semaphore.
241−limitedMapConcurrently ::
198+limitedMapConcurrently :: MonadUnliftIO m
242− (MonadUnliftIO m) =>
243− Int ->
199+ => Int -> (t -> m a) -> [t] -> m [a]
244− (t -> m a) ->
245− [t] ->
246− m [a]
247limitedMapConcurrently 0 _ _ = return []
200limitedMapConcurrently 0 _ _ = return []
248limitedMapConcurrently count act args = do
201limitedMapConcurrently count act args = do
249 t' <- U.newTVarIO count
202 t' <- U.newTVarIO count
@@ -252,15 +205,17 @@ limitedMapConcurrently count act args = do
252 where
205 where
253 wThread t arg =
206 wThread t arg =
254 U.bracket_ (waitSem t) (signalSem t) $ act arg
207 U.bracket_ (waitSem t) (signalSem t) $ act arg
208+
255 -- quantity semaphore implementation using TVar
209 -- quantity semaphore implementation using TVar
256 waitSem t = U.atomically $ do
210 waitSem t = U.atomically $ do
257 v <- U.readTVar t
211 v <- U.readTVar t
258 if v > 0
212 if v > 0
259− then U.writeTVar t (v - 1)
213+ then U.writeTVar t (v-1)
260− else U.retrySTM
214+ else U.retrySTM
215+
261 signalSem t = U.atomically $ do
216 signalSem t = U.atomically $ do
262 v <- U.readTVar t
217 v <- U.readTVar t
263− U.writeTVar t (v + 1)
218+ U.writeTVar t (v+1)
264
219
265-- helper function to 'drop' empty optional parameter.
220-- helper function to 'drop' empty optional parameter.
266mkQuery :: Text -> Maybe Text -> Maybe (Text, Text)
221mkQuery :: Text -> Maybe Text -> Maybe (Text, Text)
@@ -269,7 +224,7 @@ mkQuery k mv = (k,) <$> mv
269-- helper function to build query parameters that are optional.
224-- helper function to build query parameters that are optional.
270-- don't use it with mandatory query params with empty value.
225-- don't use it with mandatory query params with empty value.
271mkOptionalParams :: [(Text, Maybe Text)] -> HT.Query
226mkOptionalParams :: [(Text, Maybe Text)] -> HT.Query
272−mkOptionalParams params = HT.toQuery $ uncurry mkQuery <$> params
227+mkOptionalParams params = HT.toQuery $ uncurry mkQuery <$> params
273
228
274-- | Conduit that rechunks bytestrings into the given chunk
229-- | Conduit that rechunks bytestrings into the given chunk
275-- lengths. Stops after given chunk lengths are yielded. Stops if
230-- lengths. Stops after given chunk lengths are yielded. Stops if
@@ -277,9 +232,41 @@ mkOptionalParams params = HT.toQuery $ uncurry mkQuery <$> params
277-- received. Does not throw any errors.
232-- received. Does not throw any errors.
278chunkBSConduit :: (Monad m) => [Int] -> C.ConduitM ByteString ByteString m ()
233chunkBSConduit :: (Monad m) => [Int] -> C.ConduitM ByteString ByteString m ()
279chunkBSConduit [] = return ()
234chunkBSConduit [] = return ()
280−chunkBSConduit (s : ss) = do
235+chunkBSConduit (s:ss) = do
281− bs <- fmap LB.toStrict $ C.takeCE s C..| C.sinkLazy
236+ bs <- fmap LB.toStrict $ C.takeCE s C..| C.sinkLazy
282− if
283− | B.length bs == s -> C.yield bs >> chunkBSConduit ss
237+ if | B.length bs == s -> C.yield bs >> chunkBSConduit ss
284− | B.length bs > 0 -> C.yield bs
238+ | B.length bs > 0 -> C.yield bs
239+ | otherwise -> return ()
240+
241+-- | Select part sizes - the logic is that the minimum part-size will
242+-- be 64MiB.
243+selectPartSizes :: Int64 -> [(PartNumber, Int64, Int64)]
244+selectPartSizes size = uncurry (List.zip3 [1..]) $
245+ List.unzip $ loop 0 size
246+ where
247+ ceil :: Double -> Int64
248+ ceil = ceiling
249+ partSize = max minPartSize (ceil $ fromIntegral size /
250+ fromIntegral maxMultipartParts)
251+
252+ m = fromIntegral partSize
253+ loop st sz
254+ | st > sz = []
255+ | st + m >= sz = [(st, sz - st)]
285− | otherwise -> return ()
256+ | otherwise = (st, m) : loop (st + m) sz
257+
258+lookupRegionCache :: Bucket -> Minio (Maybe Region)
259+lookupRegionCache b = do
260+ rMVar <- asks mcRegionMap
261+ rMap <- UM.readMVar rMVar
262+ return $ H.lookup b rMap
263+
264+addToRegionCache :: Bucket -> Region -> Minio ()
265+addToRegionCache b region = do
266+ rMVar <- asks mcRegionMap
267+ UM.modifyMVar_ rMVar $ return . H.insert b region
268+
269+deleteFromRegionCache :: Bucket -> Minio ()
270+deleteFromRegionCache b = do
271+ rMVar <- asks mcRegionMap
272+ UM.modifyMVar_ rMVar $ return . H.delete b
src/Network/Minio/XmlCommon.hsdeleted−65
@@ -1,65 +0,0 @@
1−--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
3−--
4−-- Licensed under the Apache License, Version 2.0 (the "License");
5−-- you may not use this file except in compliance with the License.
6−-- You may obtain a copy of the License at
7−--
8−-- http://www.apache.org/licenses/LICENSE-2.0
9−--
10−-- Unless required by applicable law or agreed to in writing, software
11−-- distributed under the License is distributed on an "AS IS" BASIS,
12−-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13−-- See the License for the specific language governing permissions and
14−-- limitations under the License.
15−--
16−
17−module Network.Minio.XmlCommon where
18−
19−import qualified Data.Text as T
20−import Data.Text.Read (decimal)
21−import Data.Time (UTCTime)
22−import Data.Time.Format.ISO8601 (iso8601ParseM)
23−import Lib.Prelude (throwIO)
24−import Network.Minio.Errors
25−import Text.XML (Name (Name), def, parseLBS)
26−import Text.XML.Cursor (Axis, Cursor, content, element, fromDocument, laxElement, ($/), (&/))
27−
28−s3Name :: Text -> Text -> Name
29−s3Name ns s = Name s (Just ns) Nothing
30−
31−uncurry4 :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e
32−uncurry4 f (a, b, c, d) = f a b c d
33−
34−uncurry6 :: (a -> b -> c -> d -> e -> f -> g) -> (a, b, c, d, e, f) -> g
35−uncurry6 f (a, b, c, d, e, g) = f a b c d e g
36−
37−-- | Parse time strings from XML
38−parseS3XMLTime :: (MonadIO m) => Text -> m UTCTime
39−parseS3XMLTime t =
40− maybe (throwIO $ MErrVXmlParse $ "timestamp parse failure: " <> t) return $
41− iso8601ParseM $
42− toString t
43−
44−parseDecimal :: (MonadIO m, Integral a) => Text -> m a
45−parseDecimal numStr =
46− either (throwIO . MErrVXmlParse . show) return $
47− fst <$> decimal numStr
48−
49−parseDecimals :: (MonadIO m, Integral a) => [Text] -> m [a]
50−parseDecimals numStr = forM numStr parseDecimal
51−
52−s3Elem :: Text -> Text -> Axis
53−s3Elem ns = element . s3Name ns
54−
55−parseRoot :: (MonadIO m) => LByteString -> m Cursor
56−parseRoot =
57− either (throwIO . MErrVXmlParse . show) (return . fromDocument)
58− . parseLBS def
59−
60−parseErrResponse :: (MonadIO m) => LByteString -> m ServiceErr
61−parseErrResponse xmldata = do
62− r <- parseRoot xmldata
63− let code = T.concat $ r $/ laxElement "Code" &/ content
64− message = T.concat $ r $/ laxElement "Message" &/ content
65− return $ toServiceErr code message
src/Network/Minio/XmlGenerator.hs+99−165
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -15,112 +15,89 @@
15--
15--
16
16
17module Network.Minio.XmlGenerator
17module Network.Minio.XmlGenerator
18− ( mkCreateBucketConfig,
18+ ( mkCreateBucketConfig
19− mkCompleteMultipartUploadRequest,
19+ , mkCompleteMultipartUploadRequest
20− mkPutNotificationRequest,
20+ , mkPutNotificationRequest
21− mkSelectRequest,
21+ , mkSelectRequest
22− )
23−where
22+ ) where
23+
24
24
25import qualified Data.ByteString.Lazy as LBS
25import qualified Data.ByteString.Lazy as LBS
26−import qualified Data.Text as T
26+import qualified Data.HashMap.Strict as H
27−import Network.Minio.Data
27+import qualified Data.Text as T
28−import Network.Minio.XmlCommon
29−import Text.XML
28+import Text.XML
29+
30+import Lib.Prelude
31+
32+import Network.Minio.Data
33+
30
34
31-- | Create a bucketConfig request body XML
35-- | Create a bucketConfig request body XML
32mkCreateBucketConfig :: Text -> Region -> ByteString
36mkCreateBucketConfig :: Text -> Region -> ByteString
33mkCreateBucketConfig ns location = LBS.toStrict $ renderLBS def bucketConfig
37mkCreateBucketConfig ns location = LBS.toStrict $ renderLBS def bucketConfig
34 where
38 where
35− s3Element n = Element (s3Name ns n) mempty
39+ s3Element n = Element (s3Name ns n) mempty
36− root =
37− s3Element
38− "CreateBucketConfiguration"
40+ root = s3Element "CreateBucketConfiguration"
39− [ NodeElement $
40− s3Element
41− "LocationConstraint"
41+ [ NodeElement $ s3Element "LocationConstraint"
42− [NodeContent location]
42+ [ NodeContent location]
43 ]
43 ]
44− bucketConfig = Document (Prologue [] Nothing []) root []
44+ bucketConfig = Document (Prologue [] Nothing []) root []
45
45
46-- | Create a completeMultipartUpload request body XML
46-- | Create a completeMultipartUpload request body XML
47mkCompleteMultipartUploadRequest :: [PartTuple] -> ByteString
47mkCompleteMultipartUploadRequest :: [PartTuple] -> ByteString
48mkCompleteMultipartUploadRequest partInfo =
48mkCompleteMultipartUploadRequest partInfo =
49 LBS.toStrict $ renderLBS def cmur
49 LBS.toStrict $ renderLBS def cmur
50 where
50 where
51− root =
52− Element "CompleteMultipartUpload" mempty $
51+ root = Element "CompleteMultipartUpload" mempty $
53− map (NodeElement . mkPart) partInfo
52+ map (NodeElement . mkPart) partInfo
54− mkPart (n, etag) =
53+ mkPart (n, etag) = Element "Part" mempty
55− Element
56− "Part"
57− mempty
58− [ NodeElement $
54+ [ NodeElement $ Element "PartNumber" mempty
59− Element
60− "PartNumber"
61− mempty
62− [NodeContent $ T.pack $ show n],
55+ [NodeContent $ T.pack $ show n]
63− NodeElement $
56+ , NodeElement $ Element "ETag" mempty
64− Element
65− "ETag"
66− mempty
67− [NodeContent etag]
57+ [NodeContent etag]
68− ]
58+ ]
69 cmur = Document (Prologue [] Nothing []) root []
59 cmur = Document (Prologue [] Nothing []) root []
70
60
71-- Simplified XML representation without element attributes.
61-- Simplified XML representation without element attributes.
72−data XNode
73− = XNode Text [XNode]
62+data XNode = XNode Text [XNode]
74− | XLeaf Text Text
63+ | XLeaf Text Text
75− deriving stock (Eq, Show)
64+ deriving (Eq, Show)
76
65
77toXML :: Text -> XNode -> ByteString
66toXML :: Text -> XNode -> ByteString
78−toXML ns node =
79− LBS.toStrict $
80− renderLBS def $
67+toXML ns node = LBS.toStrict $ renderLBS def $
81− Document (Prologue [] Nothing []) (xmlNode node) []
68+ Document (Prologue [] Nothing []) (xmlNode node) []
82 where
69 where
83 xmlNode :: XNode -> Element
70 xmlNode :: XNode -> Element
84− xmlNode (XNode name nodes) =
85− Element (s3Name ns name) mempty $
71+ xmlNode (XNode name nodes) = Element (s3Name ns name) mempty $
86− map (NodeElement . xmlNode) nodes
72+ map (NodeElement . xmlNode) nodes
87− xmlNode (XLeaf name content) =
73+ xmlNode (XLeaf name content) = Element (s3Name ns name) mempty
88− Element
89− (s3Name ns name)
90− mempty
91− [NodeContent content]
74+ [NodeContent content]
92
75
93class ToXNode a where
76class ToXNode a where
94 toXNode :: a -> XNode
77 toXNode :: a -> XNode
95
78
96instance ToXNode Event where
79instance ToXNode Event where
97− toXNode = XLeaf "Event" . toText
80+ toXNode = XLeaf "Event" . show
98
81
99instance ToXNode Notification where
82instance ToXNode Notification where
100− toXNode (Notification qc tc lc) =
101− XNode "NotificationConfiguration" $
83+ toXNode (Notification qc tc lc) = XNode "NotificationConfiguration" $
102− map (toXNodesWithArnName "QueueConfiguration" "Queue") qc
84+ map (toXNodesWithArnName "QueueConfiguration" "Queue") qc ++
103− ++ map (toXNodesWithArnName "TopicConfiguration" "Topic") tc
85+ map (toXNodesWithArnName "TopicConfiguration" "Topic") tc ++
104− ++ map (toXNodesWithArnName "CloudFunctionConfiguration" "CloudFunction") lc
86+ map (toXNodesWithArnName "CloudFunctionConfiguration" "CloudFunction") lc
105
87
106toXNodesWithArnName :: Text -> Text -> NotificationConfig -> XNode
88toXNodesWithArnName :: Text -> Text -> NotificationConfig -> XNode
107−toXNodesWithArnName eltName arnName (NotificationConfig itemId arn events fRule) =
89+toXNodesWithArnName eltName arnName (NotificationConfig id arn events fRule) =
108− XNode eltName $
109− [XLeaf "Id" itemId, XLeaf arnName arn]
90+ XNode eltName $ [XLeaf "Id" id, XLeaf arnName arn] ++ map toXNode events ++
110− ++ map toXNode events
111− ++ [toXNode fRule]
91+ [toXNode fRule]
112
92
113instance ToXNode Filter where
93instance ToXNode Filter where
114 toXNode (Filter (FilterKey (FilterRules rules))) =
94 toXNode (Filter (FilterKey (FilterRules rules))) =
115 XNode "Filter" [XNode "S3Key" (map getFRXNode rules)]
95 XNode "Filter" [XNode "S3Key" (map getFRXNode rules)]
116
96
117getFRXNode :: FilterRule -> XNode
97getFRXNode :: FilterRule -> XNode
118−getFRXNode (FilterRule n v) =
98+getFRXNode (FilterRule n v) = XNode "FilterRule" [ XLeaf "Name" n
119− XNode
120− "FilterRule"
121− [ XLeaf "Name" n,
122− XLeaf "Value" v
99+ , XLeaf "Value" v
123− ]
100+ ]
124
101
125mkPutNotificationRequest :: Text -> Notification -> ByteString
102mkPutNotificationRequest :: Text -> Notification -> ByteString
126mkPutNotificationRequest ns = toXML ns . toXNode
103mkPutNotificationRequest ns = toXML ns . toXNode
@@ -129,103 +106,60 @@ mkSelectRequest :: SelectRequest -> ByteString
129mkSelectRequest r = LBS.toStrict $ renderLBS def sr
106mkSelectRequest r = LBS.toStrict $ renderLBS def sr
130 where
107 where
131 sr = Document (Prologue [] Nothing []) root []
108 sr = Document (Prologue [] Nothing []) root []
132− root =
133− Element "SelectRequest" mempty $
109+ root = Element "SelectRequest" mempty $
134− [ NodeElement
110+ [ NodeElement (Element "Expression" mempty
135− ( Element
136− "Expression"
137− mempty
138− [NodeContent $ srExpression r]
111+ [NodeContent $ srExpression r])
139− ),
140− NodeElement
141− ( Element
142− "ExpressionType"
112+ , NodeElement (Element "ExpressionType" mempty
143− mempty
144− [NodeContent $ show $ srExpressionType r]
113+ [NodeContent $ show $ srExpressionType r])
145− ),
146− NodeElement
147− ( Element "InputSerialization" mempty $
114+ , NodeElement (Element "InputSerialization" mempty $
148− inputSerializationNodes $
115+ inputSerializationNodes $ srInputSerialization r)
149− srInputSerialization r
150− ),
151− NodeElement
152− ( Element "OutputSerialization" mempty $
116+ , NodeElement (Element "OutputSerialization" mempty $
153− outputSerializationNodes $
117+ outputSerializationNodes $ srOutputSerialization r)
154− srOutputSerialization r
155− )
156− ]
157− ++ maybe [] reqProgElem (srRequestProgressEnabled r)
118+ ] ++ maybe [] reqProgElem (srRequestProgressEnabled r)
158− reqProgElem enabled =
119+ reqProgElem enabled = [NodeElement
159− [ NodeElement
160− ( Element
161− "RequestProgress"
120+ (Element "RequestProgress" mempty
162− mempty
163− [ NodeElement
121+ [NodeElement
164− ( Element
165− "Enabled"
122+ (Element "Enabled" mempty
166− mempty
167− [ NodeContent
123+ [NodeContent
168− (if enabled then "TRUE" else "FALSE")
124+ (if enabled then "TRUE" else "FALSE")]
169− ]
170− )
125+ )
171− ]
126+ ]
172− )
127+ )
173− ]
128+ ]
174− inputSerializationNodes is =
175− comprTypeNode (isCompressionType is)
129+ inputSerializationNodes is = comprTypeNode (isCompressionType is) ++
176− ++ [NodeElement $ formatNode (isFormatInfo is)]
130+ [NodeElement $ formatNode (isFormatInfo is)]
177− comprTypeNode (Just c) =
131+ comprTypeNode (Just c) = [NodeElement $ Element "CompressionType" mempty
178− [ NodeElement $
179− Element
180− "CompressionType"
181− mempty
182− [ NodeContent $ case c of
132+ [NodeContent $ case c of
183− CompressionTypeNone -> "NONE"
133+ CompressionTypeNone -> "NONE"
184− CompressionTypeGzip -> "GZIP"
134+ CompressionTypeGzip -> "GZIP"
185− CompressionTypeBzip2 -> "BZIP2"
135+ CompressionTypeBzip2 -> "BZIP2"
186− ]
136+ ]
187− ]
137+ ]
188 comprTypeNode Nothing = []
138 comprTypeNode Nothing = []
139+
189 kvElement (k, v) = Element (Name k Nothing Nothing) mempty [NodeContent v]
140 kvElement (k, v) = Element (Name k Nothing Nothing) mempty [NodeContent v]
190− formatNode (InputFormatCSV c) =
141+ formatNode (InputFormatCSV (CSVProp h)) =
191− Element
142+ Element "CSV" mempty
192− "CSV"
193− mempty
194− (map (NodeElement . kvElement) (csvPropsList c))
143+ (map NodeElement $ map kvElement $ H.toList h)
195 formatNode (InputFormatJSON p) =
144 formatNode (InputFormatJSON p) =
196− Element
145+ Element "JSON" mempty
197− "JSON"
198− mempty
199− [ NodeElement
146+ [NodeElement
200− ( Element
201− "Type"
202− mempty
147+ (Element "Type" mempty
203− [ NodeContent $ case jsonipType p of
148+ [NodeContent $ case jsonipType p of
204− JSONTypeDocument -> "DOCUMENT"
149+ JSONTypeDocument -> "DOCUMENT"
205− JSONTypeLines -> "LINES"
150+ JSONTypeLines -> "LINES"
206− ]
151+ ]
207− )
152+ )
208− ]
153+ ]
209 formatNode InputFormatParquet = Element "Parquet" mempty []
154 formatNode InputFormatParquet = Element "Parquet" mempty []
155+
210 outputSerializationNodes (OutputSerializationJSON j) =
156 outputSerializationNodes (OutputSerializationJSON j) =
211− [ NodeElement
212− ( Element "JSON" mempty $
157+ [NodeElement (Element "JSON" mempty $
213− rdElem $
214− jsonopRecordDelimiter j
158+ rdElem $ jsonopRecordDelimiter j)]
215− )
216− ]
217− outputSerializationNodes (OutputSerializationCSV c) =
159+ outputSerializationNodes (OutputSerializationCSV (CSVProp h)) =
218− [ NodeElement $
160+ [NodeElement $ Element "CSV" mempty
219− Element
220− "CSV"
221− mempty
222− (map (NodeElement . kvElement) (csvPropsList c))
161+ (map NodeElement $ map kvElement $ H.toList h)]
223− ]
162+
224 rdElem Nothing = []
163 rdElem Nothing = []
225− rdElem (Just t) =
226− [ NodeElement $
227− Element
228− "RecordDelimiter"
164+ rdElem (Just t) = [NodeElement $ Element "RecordDelimiter" mempty
229− mempty
230− [NodeContent t]
165+ [NodeContent t]]
231− ]
src/Network/Minio/XmlParser.hs+154−107
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -15,38 +15,75 @@
15--
15--
16
16
17module Network.Minio.XmlParser
17module Network.Minio.XmlParser
18− ( parseListBuckets,
18+ ( parseListBuckets
19− parseLocation,
19+ , parseLocation
20− parseNewMultipartUpload,
20+ , parseNewMultipartUpload
21− parseCompleteMultipartUploadResponse,
21+ , parseCompleteMultipartUploadResponse
22− parseCopyObjectResponse,
22+ , parseCopyObjectResponse
23− parseListObjectsResponse,
23+ , parseListObjectsResponse
24− parseListObjectsV1Response,
24+ , parseListObjectsV1Response
25− parseListUploadsResponse,
25+ , parseListUploadsResponse
26− parseListPartsResponse,
26+ , parseListPartsResponse
27− parseErrResponse,
27+ , parseErrResponse
28− parseNotification,
28+ , parseNotification
29− parseSelectProgress,
29+ , parseSelectProgress
30− )
31−where
30+ ) where
32
31
33import qualified Data.ByteString.Lazy as LB
32import qualified Data.ByteString.Lazy as LB
34−import qualified Data.HashMap.Strict as H
33+import qualified Data.HashMap.Strict as H
35−import Data.List (zip4, zip6)
34+import Data.List (zip3, zip4, zip6)
36−import qualified Data.Text as T
35+import qualified Data.Text as T
36+import Data.Text.Read (decimal)
37−import Data.Time
37+import Data.Time
38−import Network.Minio.Data
38+import Text.XML
39−import Network.Minio.XmlCommon
40−import Text.XML.Cursor hiding (bool)
39+import Text.XML.Cursor hiding (bool)
40+
41+import Lib.Prelude
42+
43+import Network.Minio.Data
44+import Network.Minio.Errors
45+
46+
47+-- | Represent the time format string returned by S3 API calls.
48+s3TimeFormat :: [Char]
49+s3TimeFormat = iso8601DateFormat $ Just "%T%QZ"
50+
51+-- | Helper functions.
52+uncurry4 :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e
53+uncurry4 f (a, b, c, d) = f a b c d
54+
55+uncurry6 :: (a -> b -> c -> d -> e -> f -> g) -> (a, b, c, d, e, f) -> g
56+uncurry6 f (a, b, c, d, e, g) = f a b c d e g
57+
58+-- | Parse time strings from XML
59+parseS3XMLTime :: MonadIO m => Text -> m UTCTime
60+parseS3XMLTime t =
61+ maybe (throwIO $ MErrVXmlParse $ "timestamp parse failure: " <> t) return $
62+ parseTimeM True defaultTimeLocale s3TimeFormat $ T.unpack t
63+
64+parseDecimal :: (MonadIO m, Integral a) => Text -> m a
65+parseDecimal numStr = either (throwIO . MErrVXmlParse . show) return $
66+ fst <$> decimal numStr
67+
68+parseDecimals :: (MonadIO m, Integral a) => [Text] -> m [a]
69+parseDecimals numStr = forM numStr parseDecimal
70+
71+s3Elem :: Text -> Text -> Axis
72+s3Elem ns = element . s3Name ns
73+
74+parseRoot :: (MonadIO m) => LByteString -> m Cursor
75+parseRoot = either (throwIO . MErrVXmlParse . show) (return . fromDocument)
76+ . parseLBS def
41
77
42-- | Parse the response XML of a list buckets call.
78-- | Parse the response XML of a list buckets call.
43parseListBuckets :: (MonadReader env m, HasSvcNamespace env, MonadIO m) => LByteString -> m [BucketInfo]
79parseListBuckets :: (MonadReader env m, HasSvcNamespace env, MonadIO m) => LByteString -> m [BucketInfo]
44parseListBuckets xmldata = do
80parseListBuckets xmldata = do
45 r <- parseRoot xmldata
81 r <- parseRoot xmldata
46 ns <- asks getSvcNamespace
82 ns <- asks getSvcNamespace
83+ let
47− let s3Elem' = s3Elem ns
84+ s3Elem' = s3Elem ns
48− names = r $// s3Elem' "Bucket" &// s3Elem' "Name" &/ content
85+ names = r $// s3Elem' "Bucket" &// s3Elem' "Name" &/ content
49− timeStrings = r $// s3Elem' "Bucket" &// s3Elem' "CreationDate" &/ content
86+ timeStrings = r $// s3Elem' "Bucket" &// s3Elem' "CreationDate" &/ content
50
87
51 times <- mapM parseS3XMLTime timeStrings
88 times <- mapM parseS3XMLTime timeStrings
52 return $ zipWith BucketInfo names times
89 return $ zipWith BucketInfo names times
@@ -79,38 +116,41 @@ parseCopyObjectResponse :: (MonadReader env m, HasSvcNamespace env, MonadIO m) =
79parseCopyObjectResponse xmldata = do
116parseCopyObjectResponse xmldata = do
80 r <- parseRoot xmldata
117 r <- parseRoot xmldata
81 ns <- asks getSvcNamespace
118 ns <- asks getSvcNamespace
119+ let
82− let s3Elem' = s3Elem ns
120+ s3Elem' = s3Elem ns
83− mtimeStr = T.concat $ r $// s3Elem' "LastModified" &/ content
121+ mtimeStr = T.concat $ r $// s3Elem' "LastModified" &/ content
84
122
85 mtime <- parseS3XMLTime mtimeStr
123 mtime <- parseS3XMLTime mtimeStr
86 return (T.concat $ r $// s3Elem' "ETag" &/ content, mtime)
124 return (T.concat $ r $// s3Elem' "ETag" &/ content, mtime)
87
125
88-- | Parse the response XML of a list objects v1 call.
126-- | Parse the response XML of a list objects v1 call.
89−parseListObjectsV1Response ::
90− (MonadReader env m, HasSvcNamespace env, MonadIO m) =>
127+parseListObjectsV1Response :: (MonadReader env m, HasSvcNamespace env, MonadIO m)
91− LByteString ->
92− m ListObjectsV1Result
128+ => LByteString -> m ListObjectsV1Result
93parseListObjectsV1Response xmldata = do
129parseListObjectsV1Response xmldata = do
94 r <- parseRoot xmldata
130 r <- parseRoot xmldata
95 ns <- asks getSvcNamespace
131 ns <- asks getSvcNamespace
132+ let
96− let s3Elem' = s3Elem ns
133+ s3Elem' = s3Elem ns
97− hasMore = ["true"] == (r $/ s3Elem' "IsTruncated" &/ content)
134+ hasMore = ["true"] == (r $/ s3Elem' "IsTruncated" &/ content)
135+
98− nextMarker = listToMaybe $ r $/ s3Elem' "NextMarker" &/ content
136+ nextMarker = headMay $ r $/ s3Elem' "NextMarker" &/ content
137+
99− prefixes = r $/ s3Elem' "CommonPrefixes" &/ s3Elem' "Prefix" &/ content
138+ prefixes = r $/ s3Elem' "CommonPrefixes" &/ s3Elem' "Prefix" &/ content
139+
100− keys = r $/ s3Elem' "Contents" &/ s3Elem' "Key" &/ content
140+ keys = r $/ s3Elem' "Contents" &/ s3Elem' "Key" &/ content
101− modTimeStr = r $/ s3Elem' "Contents" &/ s3Elem' "LastModified" &/ content
141+ modTimeStr = r $/ s3Elem' "Contents" &/ s3Elem' "LastModified" &/ content
102− etagsList = r $/ s3Elem' "Contents" &/ s3Elem' "ETag" &/ content
142+ etagsList = r $/ s3Elem' "Contents" &/ s3Elem' "ETag" &/ content
103− -- if response xml contains empty etag response fill them with as
143+ -- if response xml contains empty etag response fill them with as
104− -- many empty Text for the zip4 below to work as intended.
144+ -- many empty Text for the zip4 below to work as intended.
105− etags = etagsList ++ repeat ""
145+ etags = etagsList ++ repeat ""
106− sizeStr = r $/ s3Elem' "Contents" &/ s3Elem' "Size" &/ content
146+ sizeStr = r $/ s3Elem' "Contents" &/ s3Elem' "Size" &/ content
107
147
108 modTimes <- mapM parseS3XMLTime modTimeStr
148 modTimes <- mapM parseS3XMLTime modTimeStr
109 sizes <- parseDecimals sizeStr
149 sizes <- parseDecimals sizeStr
110
150
111− let objects =
151+ let
112− map (uncurry6 ObjectInfo) $
152+ objects = map (uncurry6 ObjectInfo) $
113− zip6 keys modTimes etags sizes (repeat H.empty) (repeat H.empty)
153+ zip6 keys modTimes etags sizes (repeat H.empty) (repeat H.empty)
114
154
115 return $ ListObjectsV1Result hasMore nextMarker objects prefixes
155 return $ ListObjectsV1Result hasMore nextMarker objects prefixes
116
156
@@ -119,24 +159,28 @@ parseListObjectsResponse :: (MonadReader env m, HasSvcNamespace env, MonadIO m)
119parseListObjectsResponse xmldata = do
159parseListObjectsResponse xmldata = do
120 r <- parseRoot xmldata
160 r <- parseRoot xmldata
121 ns <- asks getSvcNamespace
161 ns <- asks getSvcNamespace
162+ let
122− let s3Elem' = s3Elem ns
163+ s3Elem' = s3Elem ns
123− hasMore = ["true"] == (r $/ s3Elem' "IsTruncated" &/ content)
164+ hasMore = ["true"] == (r $/ s3Elem' "IsTruncated" &/ content)
165+
124− nextToken = listToMaybe $ r $/ s3Elem' "NextContinuationToken" &/ content
166+ nextToken = headMay $ r $/ s3Elem' "NextContinuationToken" &/ content
167+
125− prefixes = r $/ s3Elem' "CommonPrefixes" &/ s3Elem' "Prefix" &/ content
168+ prefixes = r $/ s3Elem' "CommonPrefixes" &/ s3Elem' "Prefix" &/ content
169+
126− keys = r $/ s3Elem' "Contents" &/ s3Elem' "Key" &/ content
170+ keys = r $/ s3Elem' "Contents" &/ s3Elem' "Key" &/ content
127− modTimeStr = r $/ s3Elem' "Contents" &/ s3Elem' "LastModified" &/ content
171+ modTimeStr = r $/ s3Elem' "Contents" &/ s3Elem' "LastModified" &/ content
128− etagsList = r $/ s3Elem' "Contents" &/ s3Elem' "ETag" &/ content
172+ etagsList = r $/ s3Elem' "Contents" &/ s3Elem' "ETag" &/ content
129− -- if response xml contains empty etag response fill them with as
173+ -- if response xml contains empty etag response fill them with as
130− -- many empty Text for the zip4 below to work as intended.
174+ -- many empty Text for the zip4 below to work as intended.
131− etags = etagsList ++ repeat ""
175+ etags = etagsList ++ repeat ""
132− sizeStr = r $/ s3Elem' "Contents" &/ s3Elem' "Size" &/ content
176+ sizeStr = r $/ s3Elem' "Contents" &/ s3Elem' "Size" &/ content
133
177
134 modTimes <- mapM parseS3XMLTime modTimeStr
178 modTimes <- mapM parseS3XMLTime modTimeStr
135 sizes <- parseDecimals sizeStr
179 sizes <- parseDecimals sizeStr
136
180
137− let objects =
181+ let
138− map (uncurry6 ObjectInfo) $
182+ objects = map (uncurry6 ObjectInfo) $
139− zip6 keys modTimes etags sizes (repeat H.empty) (repeat H.empty)
183+ zip6 keys modTimes etags sizes (repeat H.empty) (repeat H.empty)
140
184
141 return $ ListObjectsResult hasMore nextToken objects prefixes
185 return $ ListObjectsResult hasMore nextToken objects prefixes
142
186
@@ -145,18 +189,20 @@ parseListUploadsResponse :: (MonadReader env m, HasSvcNamespace env, MonadIO m)
145parseListUploadsResponse xmldata = do
189parseListUploadsResponse xmldata = do
146 r <- parseRoot xmldata
190 r <- parseRoot xmldata
147 ns <- asks getSvcNamespace
191 ns <- asks getSvcNamespace
192+ let
148− let s3Elem' = s3Elem ns
193+ s3Elem' = s3Elem ns
149− hasMore = ["true"] == (r $/ s3Elem' "IsTruncated" &/ content)
194+ hasMore = ["true"] == (r $/ s3Elem' "IsTruncated" &/ content)
150− prefixes = r $/ s3Elem' "CommonPrefixes" &/ s3Elem' "Prefix" &/ content
195+ prefixes = r $/ s3Elem' "CommonPrefixes" &/ s3Elem' "Prefix" &/ content
151− nextKey = listToMaybe $ r $/ s3Elem' "NextKeyMarker" &/ content
196+ nextKey = headMay $ r $/ s3Elem' "NextKeyMarker" &/ content
152− nextUpload = listToMaybe $ r $/ s3Elem' "NextUploadIdMarker" &/ content
197+ nextUpload = headMay $ r $/ s3Elem' "NextUploadIdMarker" &/ content
153− uploadKeys = r $/ s3Elem' "Upload" &/ s3Elem' "Key" &/ content
198+ uploadKeys = r $/ s3Elem' "Upload" &/ s3Elem' "Key" &/ content
154− uploadIds = r $/ s3Elem' "Upload" &/ s3Elem' "UploadId" &/ content
199+ uploadIds = r $/ s3Elem' "Upload" &/ s3Elem' "UploadId" &/ content
155− uploadInitTimeStr = r $/ s3Elem' "Upload" &/ s3Elem' "Initiated" &/ content
200+ uploadInitTimeStr = r $/ s3Elem' "Upload" &/ s3Elem' "Initiated" &/ content
156
201
157 uploadInitTimes <- mapM parseS3XMLTime uploadInitTimeStr
202 uploadInitTimes <- mapM parseS3XMLTime uploadInitTimeStr
158
203
204+ let
159− let uploads = zip3 uploadKeys uploadIds uploadInitTimes
205+ uploads = zip3 uploadKeys uploadIds uploadInitTimes
160
206
161 return $ ListUploadsResult hasMore nextKey nextUpload uploads prefixes
207 return $ ListUploadsResult hasMore nextKey nextUpload uploads prefixes
162
208
@@ -164,25 +210,34 @@ parseListPartsResponse :: (MonadReader env m, HasSvcNamespace env, MonadIO m) =>
164parseListPartsResponse xmldata = do
210parseListPartsResponse xmldata = do
165 r <- parseRoot xmldata
211 r <- parseRoot xmldata
166 ns <- asks getSvcNamespace
212 ns <- asks getSvcNamespace
213+ let
167− let s3Elem' = s3Elem ns
214+ s3Elem' = s3Elem ns
168− hasMore = ["true"] == (r $/ s3Elem' "IsTruncated" &/ content)
215+ hasMore = ["true"] == (r $/ s3Elem' "IsTruncated" &/ content)
169− nextPartNumStr = listToMaybe $ r $/ s3Elem' "NextPartNumberMarker" &/ content
216+ nextPartNumStr = headMay $ r $/ s3Elem' "NextPartNumberMarker" &/ content
170− partNumberStr = r $/ s3Elem' "Part" &/ s3Elem' "PartNumber" &/ content
217+ partNumberStr = r $/ s3Elem' "Part" &/ s3Elem' "PartNumber" &/ content
171− partModTimeStr = r $/ s3Elem' "Part" &/ s3Elem' "LastModified" &/ content
218+ partModTimeStr = r $/ s3Elem' "Part" &/ s3Elem' "LastModified" &/ content
172− partETags = r $/ s3Elem' "Part" &/ s3Elem' "ETag" &/ content
219+ partETags = r $/ s3Elem' "Part" &/ s3Elem' "ETag" &/ content
173− partSizeStr = r $/ s3Elem' "Part" &/ s3Elem' "Size" &/ content
220+ partSizeStr = r $/ s3Elem' "Part" &/ s3Elem' "Size" &/ content
174
221
175 partModTimes <- mapM parseS3XMLTime partModTimeStr
222 partModTimes <- mapM parseS3XMLTime partModTimeStr
176 partSizes <- parseDecimals partSizeStr
223 partSizes <- parseDecimals partSizeStr
177 partNumbers <- parseDecimals partNumberStr
224 partNumbers <- parseDecimals partNumberStr
178 nextPartNum <- parseDecimals $ maybeToList nextPartNumStr
225 nextPartNum <- parseDecimals $ maybeToList nextPartNumStr
179
226
180− let partInfos =
227+ let
181− map (uncurry4 ObjectPartInfo) $
228+ partInfos = map (uncurry4 ObjectPartInfo) $
182− zip4 partNumbers partETags partSizes partModTimes
229+ zip4 partNumbers partETags partSizes partModTimes
183
230
184 return $ ListPartsResult hasMore (listToMaybe nextPartNum) partInfos
231 return $ ListPartsResult hasMore (listToMaybe nextPartNum) partInfos
185
232
233+
234+parseErrResponse :: (MonadIO m) => LByteString -> m ServiceErr
235+parseErrResponse xmldata = do
236+ r <- parseRoot xmldata
237+ let code = T.concat $ r $/ element "Code" &/ content
238+ message = T.concat $ r $/ element "Message" &/ content
239+ return $ toServiceErr code message
240+
186parseNotification :: (MonadReader env m, HasSvcNamespace env, MonadIO m) => LByteString -> m Notification
241parseNotification :: (MonadReader env m, HasSvcNamespace env, MonadIO m) => LByteString -> m Notification
187parseNotification xmldata = do
242parseNotification xmldata = do
188 r <- parseRoot xmldata
243 r <- parseRoot xmldata
@@ -191,40 +246,32 @@ parseNotification xmldata = do
191 qcfg = map node $ r $/ s3Elem' "QueueConfiguration"
246 qcfg = map node $ r $/ s3Elem' "QueueConfiguration"
192 tcfg = map node $ r $/ s3Elem' "TopicConfiguration"
247 tcfg = map node $ r $/ s3Elem' "TopicConfiguration"
193 lcfg = map node $ r $/ s3Elem' "CloudFunctionConfiguration"
248 lcfg = map node $ r $/ s3Elem' "CloudFunctionConfiguration"
194− Notification
195− <$> mapM (parseNode ns "Queue") qcfg
249+ Notification <$> (mapM (parseNode ns "Queue") qcfg)
196− <*> mapM (parseNode ns "Topic") tcfg
250+ <*> (mapM (parseNode ns "Topic") tcfg)
197− <*> mapM (parseNode ns "CloudFunction") lcfg
251+ <*> (mapM (parseNode ns "CloudFunction") lcfg)
198 where
252 where
253+
199 getFilterRule ns c =
254 getFilterRule ns c =
200 let name = T.concat $ c $/ s3Elem ns "Name" &/ content
255 let name = T.concat $ c $/ s3Elem ns "Name" &/ content
201 value = T.concat $ c $/ s3Elem ns "Value" &/ content
256 value = T.concat $ c $/ s3Elem ns "Value" &/ content
202− in FilterRule name value
257+ in FilterRule name value
258+
203 parseNode ns arnName nodeData = do
259 parseNode ns arnName nodeData = do
204 let c = fromNode nodeData
260 let c = fromNode nodeData
205− itemId = T.concat $ c $/ s3Elem ns "Id" &/ content
261+ id = T.concat $ c $/ s3Elem ns "Id" &/ content
206 arn = T.concat $ c $/ s3Elem ns arnName &/ content
262 arn = T.concat $ c $/ s3Elem ns arnName &/ content
207− events = mapMaybe textToEvent (c $/ s3Elem ns "Event" &/ content)
263+ events = catMaybes $ map textToEvent $ c $/ s3Elem ns "Event" &/ content
208− rules =
209− c
210− $/ s3Elem ns "Filter"
264+ rules = c $/ s3Elem ns "Filter" &/ s3Elem ns "S3Key" &/
211− &/ s3Elem ns "S3Key"
212− &/ s3Elem ns "FilterRule"
265+ s3Elem ns "FilterRule" &| getFilterRule ns
213− &| getFilterRule ns
214− return $
215− NotificationConfig
266+ return $ NotificationConfig id arn events
216− itemId
217− arn
218− events
219− (Filter $ FilterKey $ FilterRules rules)
267+ (Filter $ FilterKey $ FilterRules rules)
220−
268+
221−parseSelectProgress :: (MonadIO m) => ByteString -> m Progress
269+parseSelectProgress :: MonadIO m => ByteString -> m Progress
222parseSelectProgress xmldata = do
270parseSelectProgress xmldata = do
223− r <- parseRoot $ LB.fromStrict xmldata
271+ r <- parseRoot $ LB.fromStrict xmldata
224− let bScanned = T.concat $ r $/ element "BytesScanned" &/ content
272+ let bScanned = T.concat $ r $/ element "BytesScanned" &/ content
225− bProcessed = T.concat $ r $/ element "BytesProcessed" &/ content
273+ bProcessed = T.concat $ r $/element "BytesProcessed" &/ content
226− bReturned = T.concat $ r $/ element "BytesReturned" &/ content
274+ bReturned = T.concat $ r $/element "BytesReturned" &/ content
227− Progress
228− <$> parseDecimal bScanned
275+ Progress <$> parseDecimal bScanned
229− <*> parseDecimal bProcessed
276+ <*> parseDecimal bProcessed
230− <*> parseDecimal bReturned
277+ <*> parseDecimal bReturned
stack.yaml+4−4
@@ -15,7 +15,7 @@
15# resolver:
15# resolver:
16# name: custom-snapshot
16# name: custom-snapshot
17# location: "./custom-snapshot.yaml"
17# location: "./custom-snapshot.yaml"
18−resolver: lts-22.19
18+resolver: lts-14.6
19
19
20# User packages to be built.
20# User packages to be built.
21# Various formats can be used as shown in the example below.
21# Various formats can be used as shown in the example below.
@@ -36,17 +36,17 @@ resolver: lts-22.19
36# non-dependency (i.e. a user package), and its test suites and benchmarks
36# non-dependency (i.e. a user package), and its test suites and benchmarks
37# will not be run. This is useful for tweaking upstream packages.
37# will not be run. This is useful for tweaking upstream packages.
38packages:
38packages:
39− - "."
39+- '.'
40# Dependency packages to be pulled from upstream that are not in the resolver
40# Dependency packages to be pulled from upstream that are not in the resolver
41# (e.g., acme-missiles-0.3)
41# (e.g., acme-missiles-0.3)
42−extra-deps:
42+extra-deps: []
43− - crypton-connection-0.3.2
44
43
45# Override default flag values for local packages and extra-deps
44# Override default flag values for local packages and extra-deps
46flags: {}
45flags: {}
47
46
48# Extra package databases containing global packages
47# Extra package databases containing global packages
49extra-package-dbs: []
48extra-package-dbs: []
49+
50# Control whether we use the GHC we find on the path
50# Control whether we use the GHC we find on the path
51# system-ghc: true
51# system-ghc: true
52#
52#
stack.yaml.lockdeleted−19
@@ -1,19 +0,0 @@
1−# This file was autogenerated by Stack.
2−# You should not edit this file by hand.
3−# For more information, please see the documentation at:
4−# https://docs.haskellstack.org/en/stable/lock_files
5−
6−packages:
7−- completed:
8− hackage: crypton-connection-0.3.2@sha256:c7937edc25ab022bcf167703f2ec5ab73b62908e545bb587d2aa42b33cd6f6cc,1581
9− pantry-tree:
10− sha256: f986ad29b008cbe5732606e9cde1897191c486a2f1f169a4cb75fd915bce397c
11− size: 394
12− original:
13− hackage: crypton-connection-0.3.2
14−snapshots:
15−- completed:
16− sha256: e5cac927cf7ccbd52aa41476baa68b88c564ee6ddc3bc573dbf4210069287fe7
17− size: 713340
18− url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/19.yaml
19− original: lts-22.19
test/LiveServer.hs+480−754
@@ -1,5 +1,6 @@
1+{-# LANGUAGE OverloadedStrings #-}
1--
2--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
3+-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
3--
4--
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
6-- you may not use this file except in compliance with the License.
@@ -14,34 +15,37 @@
14-- limitations under the License.
15-- limitations under the License.
15--
16--
16
17
18+import qualified Test.QuickCheck as Q
19+import Test.Tasty
20+import Test.Tasty.HUnit
21+import Test.Tasty.QuickCheck as QC
22+
17−import Conduit (replicateC)
23+import Conduit (replicateC)
18−import qualified Conduit as C
24+import qualified Conduit as C
19−import qualified Control.Monad.Trans.Resource as R
25+import qualified Control.Monad.Trans.Resource as R
20−import qualified Data.ByteString as BS
26+import qualified Data.ByteString as BS
21−import Data.Conduit (yield)
27+import Data.Conduit (yield)
22−import qualified Data.Conduit.Binary as CB
28+import qualified Data.Conduit.Binary as CB
23−import Data.Conduit.Combinators (sinkList)
29+import Data.Conduit.Combinators (sinkList)
24−import qualified Data.HashMap.Strict as H
30+import qualified Data.HashMap.Strict as H
25−import qualified Data.Text as T
31+import qualified Data.Text as T
26−import Data.Time (fromGregorian)
32+import Data.Time (fromGregorian)
27−import qualified Data.Time as Time
33+import qualified Data.Time as Time
28−import Lib.Prelude
29import qualified Network.HTTP.Client.MultipartFormData as Form
34import qualified Network.HTTP.Client.MultipartFormData as Form
30−import qualified Network.HTTP.Conduit as NC
35+import qualified Network.HTTP.Conduit as NC
31−import qualified Network.HTTP.Types as HT
36+import qualified Network.HTTP.Types as HT
32−import Network.Minio
33−import Network.Minio.Credentials (Creds (CredsStatic))
34−import Network.Minio.Data
35−import Network.Minio.Data.Crypto
36−import Network.Minio.S3API
37−import Network.Minio.Utils
38−import System.Directory (getTemporaryDirectory)
37+import System.Directory (getTemporaryDirectory)
39−import qualified System.Environment as Env
38+import System.Environment (lookupEnv)
40−import qualified Test.QuickCheck as Q
39+import qualified System.IO as SIO
41−import Test.Tasty
40+
42−import Test.Tasty.HUnit
41+import Lib.Prelude
43−import Test.Tasty.QuickCheck as QC
42+
44−import qualified UnliftIO.IO as UIO
43+import Network.Minio
44+import Network.Minio.Data
45+import Network.Minio.Data.Crypto
46+import Network.Minio.PutObject
47+import Network.Minio.S3API
48+import Network.Minio.Utils
45
49
46main :: IO ()
50main :: IO ()
47main = defaultMain tests
51main = defaultMain tests
@@ -49,107 +53,47 @@ main = defaultMain tests
49tests :: TestTree
53tests :: TestTree
50tests = testGroup "Tests" [liveServerUnitTests]
54tests = testGroup "Tests" [liveServerUnitTests]
51
55
52−lookupTestEnv :: IO (Maybe ConnectInfo, (AccessKey, SecretKey))
53−lookupTestEnv = do
54− val <- Env.lookupEnv "MINIO_LOCAL"
55− secure <- Env.lookupEnv "MINIO_SECURE"
56− let localServerMaybe = case (val, secure) of
57− (Just _, Just _) -> Just "https://localhost:9000"
58− (Just _, Nothing) -> Just "http://localhost:9000"
59− _ -> Nothing
60− accessKeyE <- Env.lookupEnv "MINIO_ACCESS_KEY"
61− let accessKey = maybe "minioadmin" fromString accessKeyE
62− secretKeyE <- Env.lookupEnv "MINIO_SECRET_KEY"
63− let secretKey = maybe "minioadmin" fromString secretKeyE
64− return (localServerMaybe, (accessKey, secretKey))
65−
66−loadTestServerConnInfo :: IO ConnectInfo
67−loadTestServerConnInfo = do
68− (localServerMaybe, (ak, sk)) <- lookupTestEnv
69− return $ case localServerMaybe of
70− Just localServer -> do
71− setCreds (CredentialValue ak sk mempty) localServer
72− _ -> minioPlayCI
73−
74−loadTestServerConnInfoSTS :: IO ConnectInfo
75−loadTestServerConnInfoSTS = do
76− (localServerMaybe, (ak, sk)) <- lookupTestEnv
77− case localServerMaybe of
78− Just localServer -> do
79− let creds = CredentialValue ak sk mempty
80− assumeRole =
81− STSAssumeRole
82− { sarCredentials = creds,
83− sarOptions = defaultSTSAssumeRoleOptions
84− }
85− setSTSCredential assumeRole localServer
86− Nothing -> do
87− let creds = case connectCreds minioPlayCI of
88− CredsStatic c -> c
89− _ -> error "unexpected play creds"
90− assumeRole =
91− STSAssumeRole
92− { sarCredentials = creds,
93− sarOptions = defaultSTSAssumeRoleOptions
94− }
95− setSTSCredential assumeRole minioPlayCI
96−
97-- conduit that generates random binary stream of given length
56-- conduit that generates random binary stream of given length
98−randomDataSrc :: (MonadIO m) => Int64 -> C.ConduitM () ByteString m ()
57+randomDataSrc :: MonadIO m => Int64 -> C.ConduitM () ByteString m ()
99−randomDataSrc = genBS
58+randomDataSrc s' = genBS s'
100 where
59 where
101− concatIt bs n =
102− BS.concat $
103− replicate (fromIntegral q) bs
60+ concatIt bs n = BS.concat $ replicate (fromIntegral q) bs ++
104− ++ [BS.take (fromIntegral r) bs]
61+ [BS.take (fromIntegral r) bs]
105− where
106− (q, r) = n `divMod` fromIntegral (BS.length bs)
62+ where (q, r) = n `divMod` fromIntegral (BS.length bs)
63+
107 genBS s = do
64 genBS s = do
108 w8s <- liftIO $ generate $ Q.vectorOf 64 (Q.choose (0, 255))
65 w8s <- liftIO $ generate $ Q.vectorOf 64 (Q.choose (0, 255))
109 let byteArr64 = BS.pack w8s
66 let byteArr64 = BS.pack w8s
110 if s < oneMiB
67 if s < oneMiB
111 then yield $ concatIt byteArr64 s
68 then yield $ concatIt byteArr64 s
112− else do
113− yield $ concatIt byteArr64 oneMiB
69+ else do yield $ concatIt byteArr64 oneMiB
114− genBS (s - oneMiB)
70+ genBS (s - oneMiB)
115
71
116−mkRandFile :: (R.MonadResource m) => Int64 -> m FilePath
72+mkRandFile :: R.MonadResource m => Int64 -> m FilePath
117mkRandFile size = do
73mkRandFile size = do
118− dir <- liftIO getTemporaryDirectory
74+ dir <- liftIO $ getTemporaryDirectory
119 C.runConduit $ randomDataSrc size C..| CB.sinkTempFile dir "miniohstest.random"
75 C.runConduit $ randomDataSrc size C..| CB.sinkTempFile dir "miniohstest.random"
120
76
121funTestBucketPrefix :: Text
77funTestBucketPrefix :: Text
122funTestBucketPrefix = "miniohstest-"
78funTestBucketPrefix = "miniohstest-"
123
79
124−funTestWithBucketNoSTS ::
80+loadTestServer :: IO ConnectInfo
125− TestName ->
81+loadTestServer = do
126− (([Char] -> Minio ()) -> Bucket -> Minio ()) ->
82+ val <- lookupEnv "MINIO_LOCAL"
127− TestTree
83+ isSecure <- lookupEnv "MINIO_SECURE"
128−funTestWithBucketNoSTS t minioTest = testCaseSteps t $ \step -> do
84+ return $ case (val, isSecure) of
129− -- generate a random name for the bucket
85+ (Just _, Just _) -> setCreds (Credentials "minio" "minio123") "https://localhost:9000"
130− bktSuffix <- liftIO $ generate $ Q.vectorOf 10 (Q.choose ('a', 'z'))
86+ (Just _, Nothing) -> setCreds (Credentials "minio" "minio123") "http://localhost:9000"
131− let b = T.concat [funTestBucketPrefix, T.pack bktSuffix]
87+ (Nothing, _) -> minioPlayCI
132− liftStep = liftIO . step
88+
133− connInfo <- loadTestServerConnInfo
134− ret <- runMinio connInfo $ do
135− liftStep $ "Creating bucket for test - " ++ t
136− foundBucket <- bucketExists b
137− liftIO $ foundBucket @?= False
138− makeBucket b Nothing
139− minioTest liftStep b
140− deleteBucket b
141− isRight ret @? ("Functional test " ++ t ++ " failed => " ++ show ret)
142−
143−funTestWithBucket ::
89+funTestWithBucket :: TestName
144− TestName ->
145− (([Char] -> Minio ()) -> Bucket -> Minio ()) ->
90+ -> (([Char] -> Minio ()) -> Bucket -> Minio ()) -> TestTree
146− TestTree
147funTestWithBucket t minioTest = testCaseSteps t $ \step -> do
91funTestWithBucket t minioTest = testCaseSteps t $ \step -> do
148 -- generate a random name for the bucket
92 -- generate a random name for the bucket
149 bktSuffix <- liftIO $ generate $ Q.vectorOf 10 (Q.choose ('a', 'z'))
93 bktSuffix <- liftIO $ generate $ Q.vectorOf 10 (Q.choose ('a', 'z'))
150 let b = T.concat [funTestBucketPrefix, T.pack bktSuffix]
94 let b = T.concat [funTestBucketPrefix, T.pack bktSuffix]
151 liftStep = liftIO . step
95 liftStep = liftIO . step
152− connInfo <- loadTestServerConnInfo
96+ connInfo <- loadTestServer
153 ret <- runMinio connInfo $ do
97 ret <- runMinio connInfo $ do
154 liftStep $ "Creating bucket for test - " ++ t
98 liftStep $ "Creating bucket for test - " ++ t
155 foundBucket <- bucketExists b
99 foundBucket <- bucketExists b
@@ -159,40 +103,26 @@ funTestWithBucket t minioTest = testCaseSteps t $ \step -> do
159 deleteBucket b
103 deleteBucket b
160 isRight ret @? ("Functional test " ++ t ++ " failed => " ++ show ret)
104 isRight ret @? ("Functional test " ++ t ++ " failed => " ++ show ret)
161
105
162− connInfoSTS <- loadTestServerConnInfoSTS
163− let t' = t ++ " (with AssumeRole Credentials)"
164− ret' <- runMinio connInfoSTS $ do
165− liftStep $ "Creating bucket for test - " ++ t'
166− foundBucket <- bucketExists b
167− liftIO $ foundBucket @?= False
168− makeBucket b Nothing
169− minioTest liftStep b
170− deleteBucket b
171− isRight ret' @? ("Functional test " ++ t' ++ " failed => " ++ show ret')
172−
173liveServerUnitTests :: TestTree
106liveServerUnitTests :: TestTree
174−liveServerUnitTests =
175− testGroup
176− "Unit tests against a live server"
107+liveServerUnitTests = testGroup "Unit tests against a live server"
177− [ basicTests,
108+ [ basicTests
178− listingTest,
109+ , listingTest
179− highLevelListingTest,
110+ , highLevelListingTest
180− lowLevelMultipartTest,
111+ , lowLevelMultipartTest
181− putObjectSizeTest,
112+ , putObjectSizeTest
182− putObjectNoSizeTest,
113+ , putObjectNoSizeTest
183− multipartTest,
114+ , multipartTest
184− putObjectContentTypeTest,
115+ , putObjectContentTypeTest
185− putObjectContentLanguageTest,
116+ , putObjectContentLanguageTest
186− putObjectStorageClassTest,
117+ , putObjectStorageClassTest
187− putObjectUserMetadataTest,
118+ , putObjectUserMetadataTest
188− getObjectTest,
119+ , getObjectTest
189− copyObjectTests,
120+ , copyObjectTests
190− presignedUrlFunTest,
121+ , presignedUrlFunTest
191− presignedPostPolicyFunTest,
122+ , presignedPostPolicyFunTest
192− bucketPolicyFunTest,
123+ , bucketPolicyFunTest
193− getNPutSSECTest,
124+ , getNPutSSECTest
194− assumeRoleRequestTest
195− ]
125+ ]
196
126
197basicTests :: TestTree
127basicTests :: TestTree
198basicTests = funTestWithBucket "Basic tests" $
128basicTests = funTestWithBucket "Basic tests" $
@@ -201,109 +131,83 @@ basicTests = funTestWithBucket "Basic tests" $
201 buckets <- getService
131 buckets <- getService
202 unless (length (filter (== bucket) $ map biName buckets) == 1) $
132 unless (length (filter (== bucket) $ map biName buckets) == 1) $
203 liftIO $
133 liftIO $
204− assertFailure
134+ assertFailure ("The bucket " ++ show bucket ++
205− ( "The bucket "
206− ++ show bucket
207− ++ " was expected to exist."
135+ " was expected to exist.")
208− )
209
136
210 step "makeBucket again to check if BucketAlreadyOwnedByYou exception is raised."
137 step "makeBucket again to check if BucketAlreadyOwnedByYou exception is raised."
211 mbE <- try $ makeBucket bucket Nothing
138 mbE <- try $ makeBucket bucket Nothing
212 case mbE of
139 case mbE of
213 Left exn -> liftIO $ exn @?= BucketAlreadyOwnedByYou
140 Left exn -> liftIO $ exn @?= BucketAlreadyOwnedByYou
214− _ -> return ()
141+ _ -> return ()
215
142
216 step "makeBucket with an invalid bucket name and check for appropriate exception."
143 step "makeBucket with an invalid bucket name and check for appropriate exception."
217 invalidMBE <- try $ makeBucket "invalidBucketName" Nothing
144 invalidMBE <- try $ makeBucket "invalidBucketName" Nothing
218 case invalidMBE of
145 case invalidMBE of
219 Left exn -> liftIO $ exn @?= MErrVInvalidBucketName "invalidBucketName"
146 Left exn -> liftIO $ exn @?= MErrVInvalidBucketName "invalidBucketName"
220− _ -> return ()
147+ _ -> return ()
221
148
222 step "getLocation works"
149 step "getLocation works"
223 region <- getLocation bucket
150 region <- getLocation bucket
224 liftIO $ region == "us-east-1" @? ("Got unexpected region => " ++ show region)
151 liftIO $ region == "us-east-1" @? ("Got unexpected region => " ++ show region)
225
152
226 step "singlepart putObject works"
153 step "singlepart putObject works"
227− testFilepath <- mkRandFile 200
228− fPutObject bucket "test-file" testFilepath defaultPutObjectOptions
154+ fPutObject bucket "lsb-release" "/etc/lsb-release" defaultPutObjectOptions
229
155
230 step "fPutObject onto a non-existent bucket and check for NoSuchBucket exception"
156 step "fPutObject onto a non-existent bucket and check for NoSuchBucket exception"
231− fpE <- try $ fPutObject "nosuchbucket" "test-file-2" testFilepath defaultPutObjectOptions
157+ fpE <- try $ fPutObject "nosuchbucket" "lsb-release" "/etc/lsb-release" defaultPutObjectOptions
232 case fpE of
158 case fpE of
233 Left exn -> liftIO $ exn @?= NoSuchBucket
159 Left exn -> liftIO $ exn @?= NoSuchBucket
234− _ -> return ()
160+ _ -> return ()
235
161
236 outFile <- mkRandFile 0
162 outFile <- mkRandFile 0
237 step "simple fGetObject works"
163 step "simple fGetObject works"
238− fGetObject bucket "test-file" outFile defaultGetObjectOptions
164+ fGetObject bucket "lsb-release" outFile defaultGetObjectOptions
239
165
240 let unmodifiedTime = UTCTime (fromGregorian 2010 11 26) 69857
166 let unmodifiedTime = UTCTime (fromGregorian 2010 11 26) 69857
241 step "fGetObject an object which is modified now but requesting as un-modified in past, check for exception"
167 step "fGetObject an object which is modified now but requesting as un-modified in past, check for exception"
242− resE <-
243− try $
244− fGetObject
245− bucket
246− "test-file"
247− outFile
248− defaultGetObjectOptions
168+ resE <- try $ fGetObject bucket "lsb-release" outFile defaultGetObjectOptions {
249− { gooIfUnmodifiedSince = Just unmodifiedTime
169+ gooIfUnmodifiedSince = (Just unmodifiedTime)
250− }
170+ }
251 case resE of
171 case resE of
252 Left exn -> liftIO $ exn @?= ServiceErr "PreconditionFailed" "At least one of the pre-conditions you specified did not hold"
172 Left exn -> liftIO $ exn @?= ServiceErr "PreconditionFailed" "At least one of the pre-conditions you specified did not hold"
253− _ -> return ()
173+ _ -> return ()
254
174
255 step "fGetObject an object with no matching etag, check for exception"
175 step "fGetObject an object with no matching etag, check for exception"
256− resE1 <-
257− try $
258− fGetObject
259− bucket
260− "test-file"
261− outFile
262− defaultGetObjectOptions
176+ resE1 <- try $ fGetObject bucket "lsb-release" outFile defaultGetObjectOptions {
263− { gooIfMatch = Just "invalid-etag"
177+ gooIfMatch = (Just "invalid-etag")
264− }
178+ }
265 case resE1 of
179 case resE1 of
266 Left exn -> liftIO $ exn @?= ServiceErr "PreconditionFailed" "At least one of the pre-conditions you specified did not hold"
180 Left exn -> liftIO $ exn @?= ServiceErr "PreconditionFailed" "At least one of the pre-conditions you specified did not hold"
267− _ -> return ()
181+ _ -> return ()
268
182
269 step "fGetObject an object with no valid range, check for exception"
183 step "fGetObject an object with no valid range, check for exception"
270− resE2 <-
271− try $
272− fGetObject
273− bucket
274− "test-file"
275− outFile
276− defaultGetObjectOptions
184+ resE2 <- try $ fGetObject bucket "lsb-release" outFile defaultGetObjectOptions {
277− { gooRange = Just $ HT.ByteRangeFromTo 100 300
185+ gooRange = (Just $ HT.ByteRangeFromTo 100 200)
278− }
186+ }
279 case resE2 of
187 case resE2 of
280− Left exn -> liftIO $ exn @?= ServiceErr "InvalidRange" "The requested range is not satisfiable"
188+ Left exn -> liftIO $ exn @?= ServiceErr "InvalidRange" "The requested range is not satisfiable"
281− _ -> return ()
189+ _ -> return ()
282
190
283 step "fGetObject on object with a valid range"
191 step "fGetObject on object with a valid range"
284− fGetObject
285− bucket
286− "test-file"
287− outFile
288− defaultGetObjectOptions
192+ fGetObject bucket "lsb-release" outFile defaultGetObjectOptions {
289− { gooRange = Just $ HT.ByteRangeFrom 1
193+ gooRange = (Just $ HT.ByteRangeFrom 1)
290− }
194+ }
291
195
292 step "fGetObject a non-existent object and check for NoSuchKey exception"
196 step "fGetObject a non-existent object and check for NoSuchKey exception"
293 resE3 <- try $ fGetObject bucket "noSuchKey" outFile defaultGetObjectOptions
197 resE3 <- try $ fGetObject bucket "noSuchKey" outFile defaultGetObjectOptions
294 case resE3 of
198 case resE3 of
295 Left exn -> liftIO $ exn @?= NoSuchKey
199 Left exn -> liftIO $ exn @?= NoSuchKey
296− _ -> return ()
200+ _ -> return ()
297
201
298 step "create new multipart upload works"
202 step "create new multipart upload works"
299 uid <- newMultipartUpload bucket "newmpupload" []
203 uid <- newMultipartUpload bucket "newmpupload" []
300− liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
204+ liftIO $ (T.length uid > 0) @? ("Got an empty multipartUpload Id.")
301
205
302 step "abort a new multipart upload works"
206 step "abort a new multipart upload works"
303 abortMultipartUpload bucket "newmpupload" uid
207 abortMultipartUpload bucket "newmpupload" uid
304
208
305 step "delete object works"
209 step "delete object works"
306− deleteObject bucket "test-file"
210+ deleteObject bucket "lsb-release"
307
211
308 step "statObject test"
212 step "statObject test"
309 let object = "sample"
213 let object = "sample"
@@ -313,310 +217,257 @@ basicTests = funTestWithBucket "Basic tests" $
313
217
314 step "get metadata of the object"
218 step "get metadata of the object"
315 res <- statObject bucket object defaultGetObjectOptions
219 res <- statObject bucket object defaultGetObjectOptions
316− liftIO $ oiSize res @?= 0
220+ liftIO $ (oiSize res) @?= 0
317
221
318 step "delete object"
222 step "delete object"
319 deleteObject bucket object
223 deleteObject bucket object
320
224
321lowLevelMultipartTest :: TestTree
225lowLevelMultipartTest :: TestTree
322lowLevelMultipartTest = funTestWithBucket "Low-level Multipart Test" $
226lowLevelMultipartTest = funTestWithBucket "Low-level Multipart Test" $
323− \step bucket -> do
227+ \step bucket -> do
324− -- low-level multipart operation tests.
228+ -- low-level multipart operation tests.
325− let object = "newmpupload"
229+ let object = "newmpupload"
326− mb15 = 15 * 1024 * 1024
230+ mb15 = 15 * 1024 * 1024
327
231
328− step "Prepare for low-level multipart tests."
232+ step "Prepare for low-level multipart tests."
329− step "create new multipart upload"
233+ step "create new multipart upload"
330− uid <- newMultipartUpload bucket object []
234+ uid <- newMultipartUpload bucket object []
331− liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
235+ liftIO $ (T.length uid > 0) @? ("Got an empty multipartUpload Id.")
332
236
333− randFile <- mkRandFile mb15
237+ randFile <- mkRandFile mb15
334
238
335− step "put object parts 1 of 1"
239+ step "put object parts 1 of 1"
336− partInfo <-
337− UIO.withBinaryFile randFile UIO.ReadMode $ \h ->
240+ h <- liftIO $ SIO.openBinaryFile randFile SIO.ReadMode
338− putObjectPart bucket object uid 1 [] $ PayloadH h 0 mb15
241+ partInfo <- putObjectPart bucket object uid 1 [] $ PayloadH h 0 mb15
339
242
340− step "complete multipart"
243+ step "complete multipart"
341− void $ completeMultipartUpload bucket object uid [partInfo]
244+ void $ completeMultipartUpload bucket object uid [partInfo]
342
245
343− destFile <- mkRandFile 0
246+ destFile <- mkRandFile 0
344− step "Retrieve the created object and check size"
247+ step "Retrieve the created object and check size"
345− fGetObject bucket object destFile defaultGetObjectOptions
248+ fGetObject bucket object destFile defaultGetObjectOptions
346− gotSize <- withNewHandle destFile getFileSize
249+ gotSize <- withNewHandle destFile getFileSize
347− liftIO $
348− gotSize
349− == Right (Just mb15)
250+ liftIO $ gotSize == Right (Just mb15) @?
350− @? "Wrong file size of put file after getting"
251+ "Wrong file size of put file after getting"
351
252
352− step "Cleanup actions"
253+ step "Cleanup actions"
353− removeObject bucket object
254+ removeObject bucket object
354
255
355putObjectSizeTest :: TestTree
256putObjectSizeTest :: TestTree
356putObjectSizeTest = funTestWithBucket "PutObject of conduit source with size" $
257putObjectSizeTest = funTestWithBucket "PutObject of conduit source with size" $
357 \step bucket -> do
258 \step bucket -> do
358− -- putObject test (conduit source, size specified)
259+ -- putObject test (conduit source, size specified)
359− let obj = "msingle"
260+ let obj = "msingle"
360− mb1 = 1 * 1024 * 1024
261+ mb1 = 1 * 1024 * 1024
361
262
362− step "Prepare for putObject with from source with size."
263+ step "Prepare for putObject with from source with size."
363− rFile <- mkRandFile mb1
264+ rFile <- mkRandFile mb1
364
265
365− step "Upload single file."
266+ step "Upload single file."
366− putObject bucket obj (CB.sourceFile rFile) (Just mb1) defaultPutObjectOptions
267+ putObject bucket obj (CB.sourceFile rFile) (Just mb1) defaultPutObjectOptions
367
268
368− step "Retrieve and verify file size"
269+ step "Retrieve and verify file size"
369− destFile <- mkRandFile 0
270+ destFile <- mkRandFile 0
370− fGetObject bucket obj destFile defaultGetObjectOptions
271+ fGetObject bucket obj destFile defaultGetObjectOptions
371− gotSize <- withNewHandle destFile getFileSize
272+ gotSize <- withNewHandle destFile getFileSize
372− liftIO $
373− gotSize
374− == Right (Just mb1)
273+ liftIO $ gotSize == Right (Just mb1) @?
375− @? "Wrong file size of put file after getting"
274+ "Wrong file size of put file after getting"
376
275
377− step "Cleanup actions"
276+ step "Cleanup actions"
378− deleteObject bucket obj
277+ deleteObject bucket obj
379
278
380putObjectNoSizeTest :: TestTree
279putObjectNoSizeTest :: TestTree
381putObjectNoSizeTest = funTestWithBucket "PutObject of conduit source with no size" $
280putObjectNoSizeTest = funTestWithBucket "PutObject of conduit source with no size" $
382 \step bucket -> do
281 \step bucket -> do
383− -- putObject test (conduit source, no size specified)
282+ -- putObject test (conduit source, no size specified)
384− let obj = "mpart"
283+ let obj = "mpart"
385− mb70 = 70 * 1024 * 1024
284+ mb70 = 70 * 1024 * 1024
386
285
387− step "Prepare for putObject with from source without providing size."
286+ step "Prepare for putObject with from source without providing size."
388− rFile <- mkRandFile mb70
287+ rFile <- mkRandFile mb70
389
288
390− step "Upload multipart file."
289+ step "Upload multipart file."
391− putObject bucket obj (CB.sourceFile rFile) Nothing defaultPutObjectOptions
290+ putObject bucket obj (CB.sourceFile rFile) Nothing defaultPutObjectOptions
392
291
393− step "Retrieve and verify file size"
292+ step "Retrieve and verify file size"
394− destFile <- mkRandFile 0
293+ destFile <- mkRandFile 0
395− fGetObject bucket obj destFile defaultGetObjectOptions
294+ fGetObject bucket obj destFile defaultGetObjectOptions
396− gotSize <- withNewHandle destFile getFileSize
295+ gotSize <- withNewHandle destFile getFileSize
397− liftIO $
398− gotSize
399− == Right (Just mb70)
296+ liftIO $ gotSize == Right (Just mb70) @?
400− @? "Wrong file size of put file after getting"
297+ "Wrong file size of put file after getting"
401
298
402− step "Cleanup actions"
299+ step "Cleanup actions"
403− deleteObject bucket obj
300+ deleteObject bucket obj
404
301
405highLevelListingTest :: TestTree
302highLevelListingTest :: TestTree
406highLevelListingTest = funTestWithBucket "High-level listObjects Test" $
303highLevelListingTest = funTestWithBucket "High-level listObjects Test" $
407 \step bucket -> do
304 \step bucket -> do
408− step "High-level listObjects Test"
305+ step "High-level listObjects Test"
409− step "put 3 objects"
306+ step "put 3 objects"
307+ let expectedObjects = ["dir/o1", "dir/dir1/o2", "dir/dir2/o3", "o4"]
410− let extractObjectsFromList =
308+ extractObjectsFromList os =
411− mapM
309+ mapM (\t -> case t of
412− ( \case
413− ListItemObject o -> Just $ oiObject o
310+ ListItemObject o -> Just $ oiObject o
414− _ -> Nothing
311+ _ -> Nothing) os
415− )
312+ expectedNonRecList = ["o4", "dir/"]
416− extractObjectsAndDirsFromList =
313+ extractObjectsAndDirsFromList os =
417− map
418− ( \case
314+ map (\t -> case t of
419− ListItemObject o -> oiObject o
315+ ListItemObject o -> oiObject o
420− ListItemPrefix d -> d
316+ ListItemPrefix d -> d) os
421− )
317+
422− expectedObjects = ["dir/o1", "dir/dir1/o2", "dir/dir2/o3", "o4"]
423− expectedNonRecList = ["o4", "dir/"]
424−
425− testFilepath <- mkRandFile 200
426− forM_ expectedObjects $
318+ forM_ expectedObjects $
427− \obj -> fPutObject bucket obj testFilepath defaultPutObjectOptions
319+ \obj -> fPutObject bucket obj "/etc/lsb-release" defaultPutObjectOptions
428−
320+
429− step "High-level listing of objects"
321+ step "High-level listing of objects"
430− items <- C.runConduit $ listObjects bucket Nothing False C..| sinkList
322+ items <- C.runConduit $ listObjects bucket Nothing False C..| sinkList
431− liftIO $
432− assertEqual "Objects/Dirs match failed!" expectedNonRecList $
323+ liftIO $ assertEqual "Objects/Dirs match failed!" expectedNonRecList $
433 extractObjectsAndDirsFromList items
324 extractObjectsAndDirsFromList items
434
325
435− step "High-level recursive listing of objects"
326+ step "High-level recursive listing of objects"
436− objects <- C.runConduit $ listObjects bucket Nothing True C..| sinkList
327+ objects <- C.runConduit $ listObjects bucket Nothing True C..| sinkList
437
328
438− liftIO
439− $ assertEqual
440− "Objects match failed!"
329+ liftIO $ assertEqual "Objects match failed!"
441− (Just $ sort expectedObjects)
330+ (Just $ sort expectedObjects) $
442− $ extractObjectsFromList objects
331+ extractObjectsFromList objects
443
332
444− step "High-level listing of objects (version 1)"
333+ step "High-level listing of objects (version 1)"
445− itemsV1 <- C.runConduit $ listObjectsV1 bucket Nothing False C..| sinkList
334+ itemsV1 <- C.runConduit $ listObjectsV1 bucket Nothing False C..| sinkList
446− liftIO $
447− assertEqual "Objects/Dirs match failed!" expectedNonRecList $
335+ liftIO $ assertEqual "Objects/Dirs match failed!" expectedNonRecList $
448 extractObjectsAndDirsFromList itemsV1
336 extractObjectsAndDirsFromList itemsV1
449
337
450− step "High-level recursive listing of objects (version 1)"
338+ step "High-level recursive listing of objects (version 1)"
451− objectsV1 <-
452− C.runConduit $
453− listObjectsV1 bucket Nothing True
339+ objectsV1 <- C.runConduit $ listObjectsV1 bucket Nothing True C..|
454− C..| sinkList
340+ sinkList
455−
341+
456− liftIO
457− $ assertEqual
458− "Objects match failed!"
342+ liftIO $ assertEqual "Objects match failed!"
459− (Just $ sort expectedObjects)
343+ (Just $ sort expectedObjects) $
460− $ extractObjectsFromList objectsV1
344+ extractObjectsFromList objectsV1
461−
345+
462− let expectedPrefListing = ["dir/o1", "dir/dir1/", "dir/dir2/"]
346+ let expectedPrefListing = ["dir/o1", "dir/dir1/", "dir/dir2/"]
463− expectedPrefListingRec = Just ["dir/dir1/o2", "dir/dir2/o3", "dir/o1"]
347+ expectedPrefListingRec = Just ["dir/dir1/o2", "dir/dir2/o3", "dir/o1"]
464− step "High-level listing with prefix"
348+ step "High-level listing with prefix"
465− prefItems <- C.runConduit $ listObjects bucket (Just "dir/") False C..| sinkList
349+ prefItems <- C.runConduit $ listObjects bucket (Just "dir/") False C..| sinkList
466− liftIO
467− $ assertEqual
468− "Objects/Dirs under prefix match failed!"
350+ liftIO $ assertEqual "Objects/Dirs under prefix match failed!"
469− expectedPrefListing
470− $ extractObjectsAndDirsFromList prefItems
351+ expectedPrefListing $ extractObjectsAndDirsFromList prefItems
471−
352+
472− step "High-level listing with prefix recursive"
353+ step "High-level listing with prefix recursive"
473− prefItemsRec <- C.runConduit $ listObjects bucket (Just "dir/") True C..| sinkList
354+ prefItemsRec <- C.runConduit $ listObjects bucket (Just "dir/") True C..| sinkList
474− liftIO
475− $ assertEqual
476− "Objects/Dirs under prefix match recursive failed!"
355+ liftIO $ assertEqual "Objects/Dirs under prefix match recursive failed!"
477− expectedPrefListingRec
478− $ extractObjectsFromList prefItemsRec
356+ expectedPrefListingRec $ extractObjectsFromList prefItemsRec
479−
357+
480− step "High-level listing with prefix (version 1)"
358+ step "High-level listing with prefix (version 1)"
481− prefItemsV1 <- C.runConduit $ listObjectsV1 bucket (Just "dir/") False C..| sinkList
359+ prefItemsV1 <- C.runConduit $ listObjectsV1 bucket (Just "dir/") False C..| sinkList
482− liftIO
483− $ assertEqual
484− "Objects/Dirs under prefix match failed!"
360+ liftIO $ assertEqual "Objects/Dirs under prefix match failed!"
485− expectedPrefListing
486− $ extractObjectsAndDirsFromList prefItemsV1
361+ expectedPrefListing $ extractObjectsAndDirsFromList prefItemsV1
487−
362+
488− step "High-level listing with prefix recursive (version 1)"
363+ step "High-level listing with prefix recursive (version 1)"
489− prefItemsRecV1 <- C.runConduit $ listObjectsV1 bucket (Just "dir/") True C..| sinkList
364+ prefItemsRecV1 <- C.runConduit $ listObjectsV1 bucket (Just "dir/") True C..| sinkList
490− liftIO
491− $ assertEqual
492− "Objects/Dirs under prefix match recursive failed!"
365+ liftIO $ assertEqual "Objects/Dirs under prefix match recursive failed!"
493− expectedPrefListingRec
494− $ extractObjectsFromList prefItemsRecV1
366+ expectedPrefListingRec $ extractObjectsFromList prefItemsRecV1
495−
367+
496− step "Cleanup actions"
368+ step "Cleanup actions"
497− forM_ expectedObjects $
369+ forM_ expectedObjects $
498− \obj -> removeObject bucket obj
370+ \obj -> removeObject bucket obj
499−
371+
500− step "High-level listIncompleteUploads Test"
372+ step "High-level listIncompleteUploads Test"
501− let object = "newmpupload"
373+ let object = "newmpupload"
502− step "create 10 multipart uploads"
374+ step "create 10 multipart uploads"
503− forM_ [1 .. 10 :: Int] $ \_ -> do
375+ forM_ [1..10::Int] $ \_ -> do
376+ uid <- newMultipartUpload bucket object []
377+ liftIO $ (T.length uid > 0) @? ("Got an empty multipartUpload Id.")
378+
379+ step "High-level listing of incomplete multipart uploads"
380+ uploads <- C.runConduit $
381+ listIncompleteUploads bucket (Just "newmpupload") True C..|
382+ sinkList
383+ liftIO $ length uploads @?= 10
384+
385+ step "cleanup"
386+ forM_ uploads $ \(UploadInfo _ uid _ _) ->
387+ abortMultipartUpload bucket object uid
388+
389+ step "High-level listIncompleteParts Test"
390+ let mb5 = 5 * 1024 * 1024
391+
392+ step "create a multipart upload"
504 uid <- newMultipartUpload bucket object []
393 uid <- newMultipartUpload bucket object []
505 liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
394 liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
506
395
507− step "High-level listing of incomplete multipart uploads"
396+ step "put object parts 1..10"
508− uploads <-
397+ inputFile <- mkRandFile mb5
509− C.runConduit $
398+ h <- liftIO $ SIO.openBinaryFile inputFile SIO.ReadMode
510− listIncompleteUploads bucket (Just "newmpupload") True
399+ forM_ [1..10] $ \pnum ->
511− C..| sinkList
512− liftIO $ length uploads @?= 10
513−
514− step "cleanup"
515− forM_ uploads $ \(UploadInfo _ uid _ _) ->
516− abortMultipartUpload bucket object uid
517−
518− step "High-level listIncompleteParts Test"
519− let mb5 = 5 * 1024 * 1024
520−
521− step "create a multipart upload"
522− uid <- newMultipartUpload bucket object []
523− liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
524−
525− step "put object parts 1..10"
526− inputFile <- mkRandFile mb5
527− UIO.withBinaryFile inputFile UIO.ReadMode $ \h ->
528− forM_ [1 .. 10] $ \pnum ->
529 putObjectPart bucket object uid pnum [] $ PayloadH h 0 mb5
400 putObjectPart bucket object uid pnum [] $ PayloadH h 0 mb5
530
401
531− step "fetch list parts"
402+ step "fetch list parts"
532− incompleteParts <-
403+ incompleteParts <- C.runConduit $ listIncompleteParts bucket object uid
533− C.runConduit $
404+ C..| sinkList
534− listIncompleteParts bucket object uid
405+ liftIO $ length incompleteParts @?= 10
535− C..| sinkList
536− liftIO $ length incompleteParts @?= 10
537
406
538− step "cleanup"
407+ step "cleanup"
539− abortMultipartUpload bucket object uid
408+ abortMultipartUpload bucket object uid
540
409
541listingTest :: TestTree
410listingTest :: TestTree
542listingTest = funTestWithBucket "Listing Test" $ \step bucket -> do
411listingTest = funTestWithBucket "Listing Test" $ \step bucket -> do
543− step "listObjects' test"
412+ step "listObjects' test"
544− step "put 10 objects"
413+ step "put 10 objects"
545− let objects = (\s -> T.concat ["test-file-", T.pack (show s)]) <$> [1 .. 10 :: Int]
414+ let objects = (\s ->T.concat ["lsb-release", T.pack (show s)]) <$> [1..10::Int]
546−
415+
547− testFilepath <- mkRandFile 200
416+ forM_ [1..10::Int] $ \s ->
548− forM_ [1 .. 10 :: Int] $ \s ->
417+ fPutObject bucket (T.concat ["lsb-release", T.pack (show s)]) "/etc/lsb-release" defaultPutObjectOptions
549− fPutObject bucket (T.concat ["test-file-", T.pack (show s)]) testFilepath defaultPutObjectOptions
418+
550−
419+ step "Simple list"
551− step "Simple list"
420+ res <- listObjects' bucket Nothing Nothing Nothing Nothing
552− res <- listObjects' bucket Nothing Nothing Nothing Nothing
421+ let expectedObjects = sort objects
553− let expectedObjects = sort objects
422+ liftIO $ assertEqual "Objects match failed!" expectedObjects
554− liftIO $
423+ (map oiObject $ lorObjects res)
555− assertEqual
424+
556− "Objects match failed!"
425+ step "Simple list version 1"
557− expectedObjects
426+ resV1 <- listObjectsV1' bucket Nothing Nothing Nothing Nothing
558− (map oiObject $ lorObjects res)
427+ let expected = sort $ map (T.concat .
559−
428+ ("lsb-release":) .
560− step "Simple list version 1"
429+ (\x -> [x]) .
561− resV1 <- listObjectsV1' bucket Nothing Nothing Nothing Nothing
430+ T.pack .
562− let expected =
431+ show) [1..10::Int]
563− sort $
432+ liftIO $ assertEqual "Objects match failed!" expected
564− map
433+ (map oiObject $ lorObjects' resV1)
565− ( T.concat
434+
566− . ("test-file-" :)
435+ step "Cleanup actions"
567− . (: [])
436+ forM_ objects $ \obj -> deleteObject bucket obj
568− . T.pack
437+
569− . show
438+ step "listIncompleteUploads' test"
570− )
439+ step "create 10 multipart uploads"
571− [1 .. 10 :: Int]
440+ let object = "newmpupload"
572− liftIO $
441+ forM_ [1..10::Int] $ \_ -> do
573− assertEqual
442+ uid <- newMultipartUpload bucket object []
574− "Objects match failed!"
443+ liftIO $ (T.length uid > 0) @? ("Got an empty multipartUpload Id.")
575− expected
444+
576− (map oiObject $ lorObjects' resV1)
445+ step "list incomplete multipart uploads"
577−
446+ incompleteUploads <- listIncompleteUploads' bucket (Just "newmpupload") Nothing
578− step "Cleanup actions"
447+ Nothing Nothing Nothing
579− forM_ objects $ \obj -> deleteObject bucket obj
448+ liftIO $ (length $ lurUploads incompleteUploads) @?= 10
580−
449+
581− step "listIncompleteUploads' test"
450+ step "cleanup"
582− step "create 10 multipart uploads"
451+ forM_ (lurUploads incompleteUploads) $
583− let object = "newmpupload"
452+ \(_, uid, _) -> abortMultipartUpload bucket object uid
584− forM_ [1 .. 10 :: Int] $ \_ -> do
453+
585− uid <- newMultipartUpload bucket object []
454+ step "Basic listIncompleteParts Test"
586− liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
455+ let mb5 = 5 * 1024 * 1024
456+
457+ step "create a multipart upload"
458+ uid <- newMultipartUpload bucket object []
459+ liftIO $ (T.length uid > 0) @? ("Got an empty multipartUpload Id.")
587
460
588− step "list incomplete multipart uploads"
461+ step "put object parts 1..10"
589− incompleteUploads <-
462+ inputFile <- mkRandFile mb5
590− listIncompleteUploads'
463+ h <- liftIO $ SIO.openBinaryFile inputFile SIO.ReadMode
591− bucket
464+ forM_ [1..10] $ \pnum ->
592− (Just "newmpupload")
465+ putObjectPart bucket object uid pnum [] $ PayloadH h 0 mb5
593− Nothing
466+
594− Nothing
467+ step "fetch list parts"
595− Nothing
468+ listPartsResult <- listIncompleteParts' bucket object uid Nothing Nothing
596− Nothing
469+ liftIO $ (length $ lprParts listPartsResult) @?= 10
597− liftIO $ length (lurUploads incompleteUploads) @?= 10
470+ abortMultipartUpload bucket object uid
598−
599− step "cleanup"
600− forM_ (lurUploads incompleteUploads) $
601− \(_, uid, _) -> abortMultipartUpload bucket object uid
602−
603− step "Basic listIncompleteParts Test"
604− let mb5 = 5 * 1024 * 1024
605−
606− step "create a multipart upload"
607− uid <- newMultipartUpload bucket object []
608− liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
609−
610− step "put object parts 1..10"
611− inputFile <- mkRandFile mb5
612− UIO.withBinaryFile inputFile UIO.ReadMode $ \h ->
613− forM_ [1 .. 10] $ \pnum ->
614− putObjectPart bucket object uid pnum [] $ PayloadH h 0 mb5
615−
616− step "fetch list parts"
617− listPartsResult <- listIncompleteParts' bucket object uid Nothing Nothing
618− liftIO $ length (lprParts listPartsResult) @?= 10
619− abortMultipartUpload bucket object uid
620
471
621presignedUrlFunTest :: TestTree
472presignedUrlFunTest :: TestTree
622presignedUrlFunTest = funTestWithBucket "presigned Url tests" $
473presignedUrlFunTest = funTestWithBucket "presigned Url tests" $
@@ -628,47 +479,29 @@ presignedUrlFunTest = funTestWithBucket "presigned Url tests" $
628 mgr <- liftIO $ NC.newManager NC.tlsManagerSettings
479 mgr <- liftIO $ NC.newManager NC.tlsManagerSettings
629
480
630 step "PUT object presigned URL - makePresignedUrl"
481 step "PUT object presigned URL - makePresignedUrl"
631− putUrl <-
482+ putUrl <- makePresignedUrl 3600 HT.methodPut (Just bucket)
632− makePresignedUrl
483+ (Just obj) (Just "us-east-1") [] []
633− 3600
484+
634− HT.methodPut
635− (Just bucket)
636− (Just obj)
637− (Just "us-east-1")
638− []
639− []
640−
641− -- print putUrl
642 let size1 = 1000 :: Int64
485 let size1 = 1000 :: Int64
643 inputFile <- mkRandFile size1
486 inputFile <- mkRandFile size1
644
487
645 -- attempt to upload using the presigned URL
488 -- attempt to upload using the presigned URL
646 putResp <- putR size1 inputFile mgr putUrl
489 putResp <- putR size1 inputFile mgr putUrl
647− liftIO $
490+ liftIO $ (NC.responseStatus putResp == HT.status200) @?
648− (NC.responseStatus putResp == HT.status200)
491+ "presigned PUT failed"
649− @? "presigned PUT failed"
650
492
651 step "GET object presigned URL - makePresignedUrl"
493 step "GET object presigned URL - makePresignedUrl"
652− getUrl <-
494+ getUrl <- makePresignedUrl 3600 HT.methodGet (Just bucket)
653− makePresignedUrl
495+ (Just obj) (Just "us-east-1") [] []
654− 3600
655− HT.methodGet
656− (Just bucket)
657− (Just obj)
658− (Just "us-east-1")
659− []
660− []
661
496
662 getResp <- getR mgr getUrl
497 getResp <- getR mgr getUrl
663− liftIO $
498+ liftIO $ (NC.responseStatus getResp == HT.status200) @?
664− (NC.responseStatus getResp == HT.status200)
499+ "presigned GET failed"
665− @? "presigned GET failed"
666
500
667 -- read content from file to compare with response above
501 -- read content from file to compare with response above
668 bs <- C.runConduit $ CB.sourceFile inputFile C..| CB.sinkLbs
502 bs <- C.runConduit $ CB.sourceFile inputFile C..| CB.sinkLbs
669− liftIO $
503+ liftIO $ (bs == NC.responseBody getResp) @?
670− (bs == NC.responseBody getResp)
504+ "presigned put and get got mismatched data"
671− @? "presigned put and get got mismatched data"
672
505
673 step "PUT object presigned - presignedPutObjectURL"
506 step "PUT object presigned - presignedPutObjectURL"
674 putUrl2 <- presignedPutObjectUrl bucket obj2 604800 []
507 putUrl2 <- presignedPutObjectUrl bucket obj2 604800 []
@@ -677,71 +510,66 @@ presignedUrlFunTest = funTestWithBucket "presigned Url tests" $
677 testFile <- mkRandFile size2
510 testFile <- mkRandFile size2
678
511
679 putResp2 <- putR size2 testFile mgr putUrl2
512 putResp2 <- putR size2 testFile mgr putUrl2
680− liftIO $
513+ liftIO $ (NC.responseStatus putResp2 == HT.status200) @?
681− (NC.responseStatus putResp2 == HT.status200)
514+ "presigned PUT failed (presignedPutObjectUrl)"
682− @? "presigned PUT failed (presignedPutObjectUrl)"
683
515
684 step "HEAD object presigned URL - presignedHeadObjectUrl"
516 step "HEAD object presigned URL - presignedHeadObjectUrl"
685 headUrl <- presignedHeadObjectUrl bucket obj2 3600 []
517 headUrl <- presignedHeadObjectUrl bucket obj2 3600 []
686
518
687− headResp <- do
519+ headResp <- do let req = NC.parseRequest_ $ toS headUrl
688− let req = NC.parseRequest_ $ decodeUtf8 headUrl
520+ NC.httpLbs (req {NC.method = HT.methodHead}) mgr
689− NC.httpLbs (req {NC.method = HT.methodHead}) mgr
521+ liftIO $ (NC.responseStatus headResp == HT.status200) @?
690− liftIO $
522+ "presigned HEAD failed (presignedHeadObjectUrl)"
691− (NC.responseStatus headResp == HT.status200)
692− @? "presigned HEAD failed (presignedHeadObjectUrl)"
693
523
694 -- check that header info is accurate
524 -- check that header info is accurate
695 let h = H.fromList $ NC.responseHeaders headResp
525 let h = H.fromList $ NC.responseHeaders headResp
696 cLen = H.lookupDefault "0" HT.hContentLength h
526 cLen = H.lookupDefault "0" HT.hContentLength h
697− liftIO $ (cLen == showBS size2) @? "Head req returned bad content length"
527+ liftIO $ (cLen == show size2) @? "Head req returned bad content length"
698
528
699 step "GET object presigned URL - presignedGetObjectUrl"
529 step "GET object presigned URL - presignedGetObjectUrl"
700 getUrl2 <- presignedGetObjectUrl bucket obj2 3600 [] []
530 getUrl2 <- presignedGetObjectUrl bucket obj2 3600 [] []
701
531
702 getResp2 <- getR mgr getUrl2
532 getResp2 <- getR mgr getUrl2
703− liftIO $
533+ liftIO $ (NC.responseStatus getResp2 == HT.status200) @?
704− (NC.responseStatus getResp2 == HT.status200)
534+ "presigned GET failed (presignedGetObjectUrl)"
705− @? "presigned GET failed (presignedGetObjectUrl)"
706
535
707 -- read content from file to compare with response above
536 -- read content from file to compare with response above
708 bs2 <- C.runConduit $ CB.sourceFile testFile C..| CB.sinkLbs
537 bs2 <- C.runConduit $ CB.sourceFile testFile C..| CB.sinkLbs
709− liftIO $
538+ liftIO $ (bs2 == NC.responseBody getResp2) @?
710− (bs2 == NC.responseBody getResp2)
539+ "presigned put and get got mismatched data (presigned*Url)"
711− @? "presigned put and get got mismatched data (presigned*Url)"
540+
712
541
713 mapM_ (removeObject bucket) [obj, obj2]
542 mapM_ (removeObject bucket) [obj, obj2]
714 where
543 where
715 putR size filePath mgr url = do
544 putR size filePath mgr url = do
716− let req = NC.parseRequest_ $ decodeUtf8 url
545+ let req = NC.parseRequest_ $ toS url
717− let req' =
546+ let req' = req { NC.method = HT.methodPut
718− req
547+ , NC.requestBody = NC.requestBodySource size $
719− { NC.method = HT.methodPut,
548+ CB.sourceFile filePath}
720− NC.requestBody =
721− NC.requestBodySource size $
722− CB.sourceFile filePath
723− }
724 NC.httpLbs req' mgr
549 NC.httpLbs req' mgr
550+
725 getR mgr url = do
551 getR mgr url = do
726− let req = NC.parseRequest_ $ decodeUtf8 url
552+ let req = NC.parseRequest_ $ toS url
727 NC.httpLbs req mgr
553 NC.httpLbs req mgr
728
554
729presignedPostPolicyFunTest :: TestTree
555presignedPostPolicyFunTest :: TestTree
730−presignedPostPolicyFunTest = funTestWithBucketNoSTS "Presigned Post Policy tests" $
556+presignedPostPolicyFunTest = funTestWithBucket "Presigned Post Policy tests" $
731 \step bucket -> do
557 \step bucket -> do
558+
732 step "presignedPostPolicy basic test"
559 step "presignedPostPolicy basic test"
733− now <- liftIO Time.getCurrentTime
560+ now <- liftIO $ Time.getCurrentTime
734
561
735 let key = "presignedPostPolicyTest/myfile"
562 let key = "presignedPostPolicyTest/myfile"
736− policyConds =
563+ policyConds = [ ppCondBucket bucket
737− [ ppCondBucket bucket,
564+ , ppCondKey key
738− ppCondKey key,
565+ , ppCondContentLengthRange 1 1000
739− ppCondContentLengthRange 1 1000,
566+ , ppCondContentType "application/octet-stream"
740− ppCondContentType "application/octet-stream",
567+ , ppCondSuccessActionStatus 200
741− ppCondSuccessActionStatus 200
568+ ]
742− ]
569+
743 expirationTime = Time.addUTCTime 3600 now
570 expirationTime = Time.addUTCTime 3600 now
744 postPolicyE = newPostPolicy expirationTime policyConds
571 postPolicyE = newPostPolicy expirationTime policyConds
572+
745 size = 1000 :: Int64
573 size = 1000 :: Int64
746
574
747 inputFile <- mkRandFile size
575 inputFile <- mkRandFile size
@@ -752,35 +580,35 @@ presignedPostPolicyFunTest = funTestWithBucketNoSTS "Presigned Post Policy tests
752 (url, formData) <- presignedPostPolicy postPolicy
580 (url, formData) <- presignedPostPolicy postPolicy
753 -- liftIO (print url) >> liftIO (print formData)
581 -- liftIO (print url) >> liftIO (print formData)
754 result <- liftIO $ postForm url formData inputFile
582 result <- liftIO $ postForm url formData inputFile
755− liftIO $
583+ liftIO $ (NC.responseStatus result == HT.status200) @?
756− (NC.responseStatus result == HT.status200)
584+ "presigned POST failed"
757− @? "presigned POST failed"
758
585
759 mapM_ (removeObject bucket) [key]
586 mapM_ (removeObject bucket) [key]
760− where
587+ where
761− postForm url formData inputFile = do
588+
762− req <- NC.parseRequest $ decodeUtf8 url
589+ postForm url formData inputFile = do
763− let parts =
590+ req <- NC.parseRequest $ toS url
764− map (uncurry Form.partBS) $
591+ let parts = map (\(x, y) -> Form.partBS x y) $
765− H.toList formData
592+ H.toList formData
766− parts' = parts ++ [Form.partFile "file" inputFile]
593+ parts' = parts ++ [Form.partFile "file" inputFile]
767− req' <- Form.formDataBody parts' req
594+ req' <- Form.formDataBody parts' req
768− mgr <- NC.newManager NC.tlsManagerSettings
595+ mgr <- NC.newManager NC.tlsManagerSettings
769− NC.httpLbs req' mgr
596+ NC.httpLbs req' mgr
770
597
771bucketPolicyFunTest :: TestTree
598bucketPolicyFunTest :: TestTree
772bucketPolicyFunTest = funTestWithBucket "Bucket Policy tests" $
599bucketPolicyFunTest = funTestWithBucket "Bucket Policy tests" $
773 \step bucket -> do
600 \step bucket -> do
601+
774 step "bucketPolicy basic test - no policy exception"
602 step "bucketPolicy basic test - no policy exception"
775 resE <- try $ getBucketPolicy bucket
603 resE <- try $ getBucketPolicy bucket
776 case resE of
604 case resE of
777 Left exn -> liftIO $ exn @?= ServiceErr "NoSuchBucketPolicy" "The bucket policy does not exist"
605 Left exn -> liftIO $ exn @?= ServiceErr "NoSuchBucketPolicy" "The bucket policy does not exist"
778− _ -> return ()
606+ _ -> return ()
779
607
780 resE' <- try $ setBucketPolicy bucket T.empty
608 resE' <- try $ setBucketPolicy bucket T.empty
781 case resE' of
609 case resE' of
782 Left exn -> liftIO $ exn @?= ServiceErr "NoSuchBucketPolicy" "The bucket policy does not exist"
610 Left exn -> liftIO $ exn @?= ServiceErr "NoSuchBucketPolicy" "The bucket policy does not exist"
783− _ -> return ()
611+ _ -> return ()
784
612
785 let expectedPolicyJSON = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::testbucket\"]},{\"Action\":[\"s3:GetObject\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::testbucket/*\"]}]}"
613 let expectedPolicyJSON = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::testbucket\"]},{\"Action\":[\"s3:GetObject\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::testbucket/*\"]}]}"
786
614
@@ -788,7 +616,7 @@ bucketPolicyFunTest = funTestWithBucket "Bucket Policy tests" $
788 resE'' <- try $ setBucketPolicy bucket expectedPolicyJSON
616 resE'' <- try $ setBucketPolicy bucket expectedPolicyJSON
789 case resE'' of
617 case resE'' of
790 Left exn -> liftIO $ exn @?= ServiceErr "MalformedPolicy" "bucket name does not match"
618 Left exn -> liftIO $ exn @?= ServiceErr "MalformedPolicy" "bucket name does not match"
791− _ -> return ()
619+ _ -> return ()
792
620
793 let expectedPolicyJSON' = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::" <> bucket <> "\"]},{\"Action\":[\"s3:GetObject\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::" <> bucket <> "/*\"]}]}"
621 let expectedPolicyJSON' = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::" <> bucket <> "\"]},{\"Action\":[\"s3:GetObject\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::" <> bucket <> "/*\"]}]}"
794
622
@@ -803,22 +631,13 @@ bucketPolicyFunTest = funTestWithBucket "Bucket Policy tests" $
803 step "verify bucket policy: (2) get `myobject` anonymously"
631 step "verify bucket policy: (2) get `myobject` anonymously"
804 connInfo <- asks mcConnInfo
632 connInfo <- asks mcConnInfo
805 let proto = bool "http://" "https://" $ connectIsSecure connInfo
633 let proto = bool "http://" "https://" $ connectIsSecure connInfo
806− url =
634+ url = BS.concat [proto, getHostAddr connInfo, "/", toS bucket,
807− BS.concat
635+ "/", toS obj]
808− [ proto,
636+ respE <- liftIO $ (fmap (Right . toS) $ NC.simpleHttp $ toS url) `catch`
809− getHostAddr connInfo,
637+ (\(e :: NC.HttpException) -> return $ Left (show e :: Text))
810− "/",
811− encodeUtf8 bucket,
812− "/",
813− encodeUtf8 obj
814− ]
815− respE <-
816− liftIO $
817− fmap (Right . toStrictBS) (NC.simpleHttp $ decodeUtf8 url)
818− `catch` (\(e :: NC.HttpException) -> return $ Left (show e :: Text))
819 case respE of
638 case respE of
820− Left err -> liftIO $ assertFailure $ show err
639+ Left err -> liftIO $ assertFailure $ show err
821− Right s -> liftIO $ s @?= BS.concat (replicate 100 "c")
640+ Right s -> liftIO $ s @?= (BS.concat $ replicate 100 "c")
822
641
823 deleteObject bucket obj
642 deleteObject bucket obj
824
643
@@ -828,28 +647,25 @@ bucketPolicyFunTest = funTestWithBucket "Bucket Policy tests" $
828multipartTest :: TestTree
647multipartTest :: TestTree
829multipartTest = funTestWithBucket "Multipart Tests" $
648multipartTest = funTestWithBucket "Multipart Tests" $
830 \step bucket -> do
649 \step bucket -> do
831− -- Commenting out test since it's platform specific.
650+ step "Prepare for putObjectInternal with non-seekable file, with size."
832− -- FIXME: Need to find a platform agnostic way to test this.
651+ step "Upload multipart file."
833− -- step "Prepare for putObjectInternal with non-seekable file, with size."
652+ let mb80 = 80 * 1024 * 1024
834− -- step "Upload multipart file."
653+ obj = "mpart"
835− -- let mb80 = 80 * 1024 * 1024
836− -- obj = "mpart"
837
654
838− -- void $ putObjectInternal bucket obj defaultPutObjectOptions $ ODFile "/dev/zero" (Just mb80)
655+ void $ putObjectInternal bucket obj defaultPutObjectOptions $ ODFile "/dev/zero" (Just mb80)
839
656
840− -- step "Retrieve and verify file size"
657+ step "Retrieve and verify file size"
841− -- destFile <- mkRandFile 0
658+ destFile <- mkRandFile 0
842− -- fGetObject bucket obj destFile defaultGetObjectOptions
659+ fGetObject bucket obj destFile defaultGetObjectOptions
843− -- gotSize <- withNewHandle destFile getFileSize
660+ gotSize <- withNewHandle destFile getFileSize
844− -- liftIO $
661+ liftIO $ gotSize == Right (Just mb80) @?
845− -- gotSize == Right (Just mb80)
662+ "Wrong file size of put file after getting"
846− -- @? "Wrong file size of put file after getting"
847
663
848− -- step "Cleanup actions"
664+ step "Cleanup actions"
849− -- removeObject bucket obj
665+ removeObject bucket obj
850
666
851− -- step "cleanup"
667+ step "cleanup"
852− -- removeObject bucket "big"
668+ removeObject bucket "big"
853
669
854 step "Prepare for removeIncompleteUpload"
670 step "Prepare for removeIncompleteUpload"
855 -- low-level multipart operation tests.
671 -- low-level multipart operation tests.
@@ -863,17 +679,15 @@ multipartTest = funTestWithBucket "Multipart Tests" $
863 randFile <- mkRandFile kb5
679 randFile <- mkRandFile kb5
864
680
865 step "upload 2 parts"
681 step "upload 2 parts"
866− forM_ [1, 2] $ \partNum -> do
682+ forM_ [1,2] $ \partNum -> do
867− UIO.withBinaryFile randFile UIO.ReadMode $ \h ->
683+ h <- liftIO $ SIO.openBinaryFile randFile SIO.ReadMode
868− void $ putObjectPart bucket object uid partNum [] $ PayloadH h 0 kb5
684+ void $ putObjectPart bucket object uid partNum [] $ PayloadH h 0 kb5
869
685
870 step "remove ongoing upload"
686 step "remove ongoing upload"
871 removeIncompleteUpload bucket object
687 removeIncompleteUpload bucket object
872− uploads <-
688+ uploads <- C.runConduit $ listIncompleteUploads bucket (Just object) False
873− C.runConduit $
689+ C..| sinkList
874− listIncompleteUploads bucket (Just object) False
690+ liftIO $ (null uploads) @? "removeIncompleteUploads didn't complete successfully"
875− C..| sinkList
876− liftIO $ null uploads @? "removeIncompleteUploads didn't complete successfully"
877
691
878putObjectContentTypeTest :: TestTree
692putObjectContentTypeTest :: TestTree
879putObjectContentTypeTest = funTestWithBucket "putObject contentType tests" $
693putObjectContentTypeTest = funTestWithBucket "putObject contentType tests" $
@@ -884,13 +698,9 @@ putObjectContentTypeTest = funTestWithBucket "putObject contentType tests" $
884
698
885 step "create server object with content-type"
699 step "create server object with content-type"
886 inputFile <- mkRandFile size1
700 inputFile <- mkRandFile size1
887− fPutObject
701+ fPutObject bucket object inputFile defaultPutObjectOptions {
888− bucket
702+ pooContentType = Just "application/javascript"
889− object
703+ }
890− inputFile
891− defaultPutObjectOptions
892− { pooContentType = Just "application/javascript"
893− }
894
704
895 -- retrieve obj info to check
705 -- retrieve obj info to check
896 oi <- headObject bucket object []
706 oi <- headObject bucket object []
@@ -900,23 +710,16 @@ putObjectContentTypeTest = funTestWithBucket "putObject contentType tests" $
900 liftIO $ assertEqual "Content-Type did not match" (Just "application/javascript") (H.lookup "Content-Type" m)
710 liftIO $ assertEqual "Content-Type did not match" (Just "application/javascript") (H.lookup "Content-Type" m)
901
711
902 step "upload object with content-encoding set to identity"
712 step "upload object with content-encoding set to identity"
903− fPutObject
713+ fPutObject bucket object inputFile defaultPutObjectOptions {
904− bucket
714+ pooContentEncoding = Just "identity"
905− object
715+ }
906− inputFile
907− defaultPutObjectOptions
908− { pooContentEncoding = Just "identity"
909− }
910
716
911 oiCE <- headObject bucket object []
717 oiCE <- headObject bucket object []
912 let m' = oiMetadata oiCE
718 let m' = oiMetadata oiCE
913
719
914 step "Validate content-encoding"
720 step "Validate content-encoding"
915− liftIO $
721+ liftIO $ assertEqual "Content-Encoding did not match" (Just "identity")
916− assertEqual
722+ (H.lookup "Content-Encoding" m')
917− "Content-Encoding did not match"
918− (Just "identity")
919− (H.lookup "Content-Encoding" m')
920
723
921 step "Cleanup actions"
724 step "Cleanup actions"
922
725
@@ -931,24 +734,17 @@ putObjectContentLanguageTest = funTestWithBucket "putObject contentLanguage test
931
734
932 step "create server object with content-language"
735 step "create server object with content-language"
933 inputFile <- mkRandFile size1
736 inputFile <- mkRandFile size1
934− fPutObject
737+ fPutObject bucket object inputFile defaultPutObjectOptions {
935− bucket
738+ pooContentLanguage = Just "en-US"
936− object
739+ }
937− inputFile
938− defaultPutObjectOptions
939− { pooContentLanguage = Just "en-US"
940− }
941
740
942 -- retrieve obj info to check
741 -- retrieve obj info to check
943 oi <- headObject bucket object []
742 oi <- headObject bucket object []
944 let m = oiMetadata oi
743 let m = oiMetadata oi
945
744
946 step "Validate content-language"
745 step "Validate content-language"
947− liftIO $
746+ liftIO $ assertEqual "content-language did not match" (Just "en-US")
948− assertEqual
747+ (H.lookup "Content-Language" m)
949− "content-language did not match"
950− (Just "en-US")
951− (H.lookup "Content-Language" m)
952 step "Cleanup actions"
748 step "Cleanup actions"
953
749
954 removeObject bucket object
750 removeObject bucket object
@@ -963,26 +759,19 @@ putObjectUserMetadataTest = funTestWithBucket "putObject user-metadata test" $
963 step "create server object with usermetdata"
759 step "create server object with usermetdata"
964 inputFile <- mkRandFile size1
760 inputFile <- mkRandFile size1
965
761
966− fPutObject
762+ fPutObject bucket object inputFile defaultPutObjectOptions {
967− bucket
763+ pooUserMetadata = [ ("x-Amz-meta-mykey1", "myval1")
968− object
764+ , ("mykey2", "myval2")
969− inputFile
765+ ]
970− defaultPutObjectOptions
766+ }
971− { pooUserMetadata =
972− [ ("x-Amz-meta-mykey1", "myval1"),
973− ("mykey2", "myval2")
974− ]
975− }
976
767
977 step "Validate user-metadata"
768 step "Validate user-metadata"
978 -- retrieve obj info to check
769 -- retrieve obj info to check
979 oi <- headObject bucket object []
770 oi <- headObject bucket object []
980 let m = oiUserMetadata oi
771 let m = oiUserMetadata oi
981 -- need to do a case-insensitive comparison
772 -- need to do a case-insensitive comparison
982− sortedMeta =
773+ sortedMeta = sort $ map (\(k, v) -> (T.toLower k, T.toLower v)) $
983− sort $
774+ H.toList m
984− map (bimap T.toLower T.toLower) $
985− H.toList m
986 ref = sort [("mykey1", "myval1"), ("mykey2", "myval2")]
775 ref = sort [("mykey1", "myval1"), ("mykey2", "myval2")]
987
776
988 liftIO $ (sortedMeta == ref) @? "Metadata mismatch!"
777 liftIO $ (sortedMeta == ref) @? "Metadata mismatch!"
@@ -998,26 +787,19 @@ getObjectTest = funTestWithBucket "getObject test" $
998 size1 = 100 :: Int64
787 size1 = 100 :: Int64
999
788
1000 inputFile <- mkRandFile size1
789 inputFile <- mkRandFile size1
1001− fPutObject
790+ fPutObject bucket object inputFile defaultPutObjectOptions {
1002− bucket
791+ pooUserMetadata = [ ("x-Amz-meta-mykey1", "myval1")
1003− object
792+ , ("mykey2", "myval2")
1004− inputFile
793+ ]
1005− defaultPutObjectOptions
794+ }
1006− { pooUserMetadata =
1007− [ ("x-Amz-meta-mykey1", "myval1"),
1008− ("mykey2", "myval2")
1009− ]
1010− }
1011
795
1012 step "get the object - check the metadata matches"
796 step "get the object - check the metadata matches"
1013 -- retrieve obj info to check
797 -- retrieve obj info to check
1014 gor <- getObject bucket object defaultGetObjectOptions
798 gor <- getObject bucket object defaultGetObjectOptions
1015 let m = oiUserMetadata $ gorObjectInfo gor
799 let m = oiUserMetadata $ gorObjectInfo gor
1016 -- need to do a case-insensitive comparison
800 -- need to do a case-insensitive comparison
1017− sortedMeta =
801+ sortedMeta = sort $ map (\(k, v) -> (T.toLower k, T.toLower v)) $
1018− sort $
802+ H.toList m
1019− map (bimap T.toLower T.toLower) $
1020− H.toList m
1021 ref = sort [("mykey1", "myval1"), ("mykey2", "myval2")]
803 ref = sort [("mykey1", "myval1"), ("mykey2", "myval2")]
1022
804
1023 liftIO $ (sortedMeta == ref) @? "Metadata mismatch!"
805 liftIO $ (sortedMeta == ref) @? "Metadata mismatch!"
@@ -1045,21 +827,13 @@ putObjectStorageClassTest = funTestWithBucket "putObject storageClass tests" $
1045 inputFile' <- mkRandFile size1
827 inputFile' <- mkRandFile size1
1046 inputFile'' <- mkRandFile size0
828 inputFile'' <- mkRandFile size0
1047
829
1048− fPutObject
830+ fPutObject bucket object inputFile defaultPutObjectOptions {
1049− bucket
831+ pooStorageClass = Just "STANDARD"
1050− object
832+ }
1051− inputFile
833+
1052− defaultPutObjectOptions
834+ fPutObject bucket object' inputFile' defaultPutObjectOptions {
1053− { pooStorageClass = Just "STANDARD"
835+ pooStorageClass = Just "REDUCED_REDUNDANCY"
1054− }
836+ }
1055−
1056− fPutObject
1057− bucket
1058− object'
1059− inputFile'
1060− defaultPutObjectOptions
1061− { pooStorageClass = Just "REDUCED_REDUNDANCY"
1062− }
1063
837
1064 removeObject bucket object
838 removeObject bucket object
1065
839
@@ -1068,24 +842,15 @@ putObjectStorageClassTest = funTestWithBucket "putObject storageClass tests" $
1068 let m' = oiMetadata oi'
842 let m' = oiMetadata oi'
1069
843
1070 step "Validate x-amz-storage-class rrs"
844 step "Validate x-amz-storage-class rrs"
1071− liftIO $
845+ liftIO $ assertEqual "storageClass did not match" (Just "REDUCED_REDUNDANCY")
1072− assertEqual
846+ (H.lookup "X-Amz-Storage-Class" m')
1073− "storageClass did not match"
847+
1074− (Just "REDUCED_REDUNDANCY")
848+ fpE <- try $ fPutObject bucket object'' inputFile'' defaultPutObjectOptions {
1075− (H.lookup "X-Amz-Storage-Class" m')
849+ pooStorageClass = Just "INVALID_STORAGE_CLASS"
1076−
850+ }
1077− fpE <-
1078− try $
1079− fPutObject
1080− bucket
1081− object''
1082− inputFile''
1083− defaultPutObjectOptions
1084− { pooStorageClass = Just "INVALID_STORAGE_CLASS"
1085− }
1086 case fpE of
851 case fpE of
1087 Left exn -> liftIO $ exn @?= ServiceErr "InvalidStorageClass" "Invalid storage class."
852 Left exn -> liftIO $ exn @?= ServiceErr "InvalidStorageClass" "Invalid storage class."
1088− _ -> return ()
853+ _ -> return ()
1089
854
1090 step "Cleanup actions"
855 step "Cleanup actions"
1091
856
@@ -1104,7 +869,7 @@ copyObjectTests = funTestWithBucket "copyObject related tests" $
1104 fPutObject bucket object inputFile defaultPutObjectOptions
869 fPutObject bucket object inputFile defaultPutObjectOptions
1105
870
1106 step "copy object"
871 step "copy object"
1107− let srcInfo = defaultSourceInfo {srcBucket = bucket, srcObject = object}
872+ let srcInfo = defaultSourceInfo { srcBucket = bucket, srcObject = object}
1108 (etag, modTime) <- copyObjectSingle bucket objCopy srcInfo []
873 (etag, modTime) <- copyObjectSingle bucket objCopy srcInfo []
1109
874
1110 -- retrieve obj info to check
875 -- retrieve obj info to check
@@ -1115,9 +880,8 @@ copyObjectTests = funTestWithBucket "copyObject related tests" $
1115
880
1116 let isMTimeDiffOk = abs (diffUTCTime modTime t) < 1.0
881 let isMTimeDiffOk = abs (diffUTCTime modTime t) < 1.0
1117
882
1118− liftIO $
883+ liftIO $ (s == size1 && e == etag && isMTimeDiffOk) @?
1119− (s == size1 && e == etag && isMTimeDiffOk)
884+ "Copied object did not match expected."
1120− @? "Copied object did not match expected."
1121
885
1122 step "cleanup actions"
886 step "cleanup actions"
1123 removeObject bucket object
887 removeObject bucket object
@@ -1138,18 +902,12 @@ copyObjectTests = funTestWithBucket "copyObject related tests" $
1138 liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
902 liftIO $ (T.length uid > 0) @? "Got an empty multipartUpload Id."
1139
903
1140 step "put object parts 1-3"
904 step "put object parts 1-3"
1141− let srcInfo' = defaultSourceInfo {srcBucket = bucket, srcObject = srcObj}
905+ let srcInfo' = defaultSourceInfo { srcBucket = bucket, srcObject = srcObj }
1142− dstInfo' = defaultDestinationInfo {dstBucket = bucket, dstObject = copyObj}
906+ dstInfo' = defaultDestinationInfo { dstBucket = bucket, dstObject = copyObj }
1143− parts <- forM [1 .. 3] $ \p -> do
907+ parts <- forM [1..3] $ \p -> do
1144− (etag', _) <-
908+ (etag', _) <- copyObjectPart dstInfo' srcInfo'{
1145− copyObjectPart
909+ srcRange = Just $ (,) ((p-1)*mb5) ((p-1)*mb5 + (mb5 - 1))
1146− dstInfo'
910+ } uid (fromIntegral p) []
1147− srcInfo'
1148− { srcRange = Just $ (,) ((p - 1) * mb5) ((p - 1) * mb5 + (mb5 - 1))
1149− }
1150− uid
1151− (fromIntegral p)
1152− []
1153 return (fromIntegral p, etag')
911 return (fromIntegral p, etag')
1154
912
1155 step "complete multipart"
913 step "complete multipart"
@@ -1195,13 +953,11 @@ copyObjectTests = funTestWithBucket "copyObject related tests" $
1195 fPutObject bucket src inputFile' defaultPutObjectOptions
953 fPutObject bucket src inputFile' defaultPutObjectOptions
1196
954
1197 step "copy last 10MiB of object"
955 step "copy last 10MiB of object"
1198− copyObject
956+ copyObject defaultDestinationInfo { dstBucket = bucket, dstObject = copyObj } defaultSourceInfo {
1199− defaultDestinationInfo {dstBucket = bucket, dstObject = copyObj}
957+ srcBucket = bucket
1200− defaultSourceInfo
958+ , srcObject = src
1201− { srcBucket = bucket,
959+ , srcRange = Just $ (,) (5 * 1024 * 1024) (size - 1)
1202− srcObject = src,
960+ }
1203− srcRange = Just $ (,) (5 * 1024 * 1024) (size - 1)
1204− }
1205
961
1206 step "verify uploaded object"
962 step "verify uploaded object"
1207 cSize <- oiSize <$> headObject bucket copyObj []
963 cSize <- oiSize <$> headObject bucket copyObj []
@@ -1212,72 +968,42 @@ copyObjectTests = funTestWithBucket "copyObject related tests" $
1212
968
1213getNPutSSECTest :: TestTree
969getNPutSSECTest :: TestTree
1214getNPutSSECTest =
970getNPutSSECTest =
1215− funTestWithBucket "Get and Put SSE-C Test" $ \step bucket -> do
971+ funTestWithBucket "Get and Put SSE-C Test" $ \step bucket -> do
1216− -- Skip this test if the server is not using TLS as encryption is
972+ -- Skip this test if the server is not using TLS as encryption is
1217− -- disabled anyway.
973+ -- disabled anyway.
1218− isTLSConn <- asks (connectIsSecure . mcConnInfo)
974+ isTLSConn <- asks (connectIsSecure . mcConnInfo)
1219− if isTLSConn
975+ if isTLSConn
1220− then do
976+ then do step "Make an encryption key"
1221− step "Make an encryption key"
977+ key <- case mkSSECKey $ BS.pack [0..31] of
1222− key <- case mkSSECKey $ BS.pack [0 .. 31] of
978+ Nothing -> liftIO $ assertFailure "This should not happen"
1223− Nothing -> liftIO $ assertFailure "This should not happen"
979+ Just k -> return k
1224− Just k -> return k
980+
1225−
981+ let mb1 = 1024*1024
1226− let mb1 = 1024 * 1024
982+ obj = "1"
1227− obj = "1"
983+ step "Upload an object using the encryption key"
1228− step "Upload an object using the encryption key"
984+ rFile <- mkRandFile mb1
1229− rFile <- mkRandFile mb1
985+ let putOpts = defaultPutObjectOptions { pooSSE = Just $ SSEC key }
1230− let putOpts = defaultPutObjectOptions {pooSSE = Just $ SSEC key}
986+ fPutObject bucket obj rFile putOpts
1231− fPutObject bucket obj rFile putOpts
987+
1232−
988+ step "Stat object without key - should fail"
1233− step "Stat object without key - should fail"
989+ headRes <- try $ statObject bucket obj defaultGetObjectOptions
1234− headRes <- try $ statObject bucket obj defaultGetObjectOptions
990+ case headRes of
1235− case headRes of
991+ Right _ -> liftIO $ assertFailure "Cannot perform head object on encrypted object without specifying key"
1236− Right _ -> liftIO $ assertFailure "Cannot perform head object on encrypted object without specifying key"
992+ Left ex@(NC.HttpExceptionRequest _ (NC.StatusCodeException rsp _))
1237− Left ex@(NC.HttpExceptionRequest _ (NC.StatusCodeException rsp _))
993+ | NC.responseStatus rsp == HT.status400 -> return ()
1238− | NC.responseStatus rsp == HT.status400 -> return ()
994+ | otherwise -> liftIO $ assertFailure $ "Unexpected err: " ++ show ex
1239− | otherwise -> liftIO $ assertFailure $ "Unexpected err: " ++ show ex
995+ Left ex -> liftIO $ assertFailure $ "Unexpected err: " ++ show ex
1240− Left ex -> liftIO $ assertFailure $ "Unexpected err: " ++ show ex
996+
1241−
997+ step "Get file and check length"
1242− step "Get file and check length"
998+ dstFile <- mkRandFile 0
1243− dstFile <- mkRandFile 0
999+ let getOpts = defaultGetObjectOptions { gooSSECKey = Just key }
1244− let getOpts = defaultGetObjectOptions {gooSSECKey = Just key}
1000+ fGetObject bucket obj dstFile getOpts
1245− fGetObject bucket obj dstFile getOpts
1001+
1246−
1002+ gotSize <- withNewHandle dstFile getFileSize
1247− gotSize <- withNewHandle dstFile getFileSize
1003+ liftIO $ gotSize == Right (Just mb1) @?
1248− liftIO $
1004+ "Wrong file size of object when getting"
1249− gotSize
1005+
1250− == Right (Just mb1)
1006+ step "Cleanup"
1251− @? "Wrong file size of object when getting"
1007+ deleteObject bucket obj
1252−
1008+
1253− step "Cleanup"
1009+ else step "Skipping encryption test as server is not using TLS"
1254− deleteObject bucket obj
1255− else step "Skipping encryption test as server is not using TLS"
1256−
1257−assumeRoleRequestTest :: TestTree
1258−assumeRoleRequestTest = testCaseSteps "Assume Role STS API" $ \step -> do
1259− step "Load credentials"
1260− (_, (ak, sk)) <- lookupTestEnv
1261− val <- Env.lookupEnv "MINIO_LOCAL"
1262− isSecure <- Env.lookupEnv "MINIO_SECURE"
1263− let localMinioCred = Just $ CredentialValue ak sk mempty
1264− playCreds =
1265− case connectCreds minioPlayCI of
1266− CredsStatic c -> Just c
1267− _ -> Nothing
1268− (cvMay, loc) =
1269− case (val, isSecure) of
1270− (Just _, Just _) -> (localMinioCred, "https://localhost:9000")
1271− (Just _, Nothing) -> (localMinioCred, "http://localhost:9000")
1272− (Nothing, _) -> (playCreds, "https://play.min.io:9000")
1273− cv <- maybe (assertFailure "bad creds") return cvMay
1274− let assumeRole =
1275− STSAssumeRole cv $
1276− defaultSTSAssumeRoleOptions
1277− { saroLocation = Just "us-east-1",
1278− saroEndpoint = Just loc
1279− }
1280− step "AssumeRole request"
1281− res <- requestSTSCredential assumeRole
1282− let v = credentialValueText $ fst res
1283− print (v, snd res)
test/Network/Minio/API/Test.hs+61−75
@@ -15,102 +15,88 @@
15--
15--
16
16
17module Network.Minio.API.Test
17module Network.Minio.API.Test
18− ( bucketNameValidityTests,
18+ ( bucketNameValidityTests
19− objectNameValidityTests,
19+ , objectNameValidityTests
20− parseServerInfoJSONTest,
20+ , parseServerInfoJSONTest
21− parseHealStatusTest,
21+ , parseHealStatusTest
22− parseHealStartRespTest,
22+ , parseHealStartRespTest
23− )
23+ ) where
24−where
24+
25−
25+import Data.Aeson (eitherDecode)
26−import Data.Aeson (eitherDecode)
26+import Test.Tasty
27−import Network.Minio.API
27+import Test.Tasty.HUnit
28−import Network.Minio.AdminAPI
28+
29−import Test.Tasty
29+import Lib.Prelude
30−import Test.Tasty.HUnit
30+
31+import Network.Minio.AdminAPI
32+import Network.Minio.API
31
33
32assertBool' :: Bool -> Assertion
34assertBool' :: Bool -> Assertion
33assertBool' = assertBool "Test failed!"
35assertBool' = assertBool "Test failed!"
34
36
35bucketNameValidityTests :: TestTree
37bucketNameValidityTests :: TestTree
36−bucketNameValidityTests =
38+bucketNameValidityTests = testGroup "Bucket Name Validity Tests"
37− testGroup
39+ [ testCase "Too short 1" $ assertBool' $ not $ isValidBucketName ""
38− "Bucket Name Validity Tests"
40+ , testCase "Too short 2" $ assertBool' $ not $ isValidBucketName "ab"
39− [ testCase "Too short 1" $ assertBool' $ not $ isValidBucketName "",
41+ , testCase "Too long 1" $ assertBool' $ not $ isValidBucketName "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
40− testCase "Too short 2" $ assertBool' $ not $ isValidBucketName "ab",
42+ , testCase "Has upper case" $ assertBool' $ not $ isValidBucketName "ABCD"
41− testCase "Too long 1" $ assertBool' $ not $ isValidBucketName "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
43+ , testCase "Has punctuation" $ assertBool' $ not $ isValidBucketName "abc,2"
42− testCase "Has upper case" $ assertBool' $ not $ isValidBucketName "ABCD",
44+ , testCase "Has hyphen at end" $ assertBool' $ not $ isValidBucketName "abc-"
43− testCase "Has punctuation" $ assertBool' $ not $ isValidBucketName "abc,2",
45+ , testCase "Has consecutive dot" $ assertBool' $ not $ isValidBucketName "abck..eedg"
44− testCase "Has hyphen at end" $ assertBool' $ not $ isValidBucketName "abc-",
46+ , testCase "Looks like IP" $ assertBool' $ not $ isValidBucketName "10.0.0.1"
45− testCase "Has consecutive dot" $ assertBool' $ not $ isValidBucketName "abck..eedg",
47+ , testCase "Valid bucket name 1" $ assertBool' $ isValidBucketName "abcd.pqeq.rea"
46− testCase "Looks like IP" $ assertBool' $ not $ isValidBucketName "10.0.0.1",
48+ , testCase "Valid bucket name 2" $ assertBool' $ isValidBucketName "abcdedgh1d"
47− testCase "Valid bucket name 1" $ assertBool' $ isValidBucketName "abcd.pqeq.rea",
49+ , testCase "Valid bucket name 3" $ assertBool' $ isValidBucketName "abc-de-dg-h1d"
48− testCase "Valid bucket name 2" $ assertBool' $ isValidBucketName "abcdedgh1d",
50+ ]
49− testCase "Valid bucket name 3" $ assertBool' $ isValidBucketName "abc-de-dg-h1d"
50− ]
51
51
52objectNameValidityTests :: TestTree
52objectNameValidityTests :: TestTree
53−objectNameValidityTests =
53+objectNameValidityTests = testGroup "Object Name Validity Tests"
54− testGroup
54+ [ testCase "Empty name" $ assertBool' $ not $ isValidObjectName ""
55− "Object Name Validity Tests"
55+ , testCase "Has unicode characters" $ assertBool' $ isValidObjectName "日本国"
56− [ testCase "Empty name" $ assertBool' $ not $ isValidObjectName "",
56+ ]
57− testCase "Has unicode characters" $ assertBool' $ isValidObjectName "日本国"
58− ]
59
57
60parseServerInfoJSONTest :: TestTree
58parseServerInfoJSONTest :: TestTree
61−parseServerInfoJSONTest =
59+parseServerInfoJSONTest = testGroup "Parse MinIO Admin API ServerInfo JSON test" $
62− testGroup "Parse MinIO Admin API ServerInfo JSON test" $
60+ map (\(tName, tDesc, tfn, tVal) -> testCase tName $ assertBool tDesc $
63− map
61+ tfn (eitherDecode tVal :: Either [Char] [ServerInfo])) testCases
64− ( \(tName, tDesc, tfn, tVal) ->
65− testCase tName $
66− assertBool tDesc $
67− tfn (eitherDecode tVal :: Either [Char] [ServerInfo])
68− )
69− testCases
70 where
62 where
71− testCases =
63+ testCases = [ ("FSBackend", "Verify server info json parsing for FS backend", isRight, fsJSON)
72− [ ("FSBackend", "Verify server info json parsing for FS backend", isRight, fsJSON),
64+ , ("Erasure Backend", "Verify server info json parsing for Erasure backend", isRight, erasureJSON)
73− ("Erasure Backend", "Verify server info json parsing for Erasure backend", isRight, erasureJSON),
65+ , ("Unknown Backend", "Verify server info json parsing for invalid backend", isLeft, invalidJSON)
74− ("Unknown Backend", "Verify server info json parsing for invalid backend", isLeft, invalidJSON)
66+ ]
75− ]
76 fsJSON = "[{\"error\":\"\",\"addr\":\"192.168.1.218:9000\",\"data\":{\"storage\":{\"Used\":20530,\"Backend\":{\"Type\":1,\"OnlineDisks\":0,\"OfflineDisks\":0,\"StandardSCData\":0,\"StandardSCParity\":0,\"RRSCData\":0,\"RRSCParity\":0,\"Sets\":null}},\"network\":{\"transferred\":808,\"received\":1160},\"http\":{\"totalHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalGETs\":{\"count\":1,\"avgDuration\":\"0s\"},\"successGETs\":{\"count\":1,\"avgDuration\":\"0s\"},\"totalPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"}},\"server\":{\"uptime\":5992503019270,\"version\":\"DEVELOPMENT.GOGET\",\"commitID\":\"DEVELOPMENT.GOGET\",\"region\":\"\",\"sqsARN\":[]}}}]"
67 fsJSON = "[{\"error\":\"\",\"addr\":\"192.168.1.218:9000\",\"data\":{\"storage\":{\"Used\":20530,\"Backend\":{\"Type\":1,\"OnlineDisks\":0,\"OfflineDisks\":0,\"StandardSCData\":0,\"StandardSCParity\":0,\"RRSCData\":0,\"RRSCParity\":0,\"Sets\":null}},\"network\":{\"transferred\":808,\"received\":1160},\"http\":{\"totalHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalGETs\":{\"count\":1,\"avgDuration\":\"0s\"},\"successGETs\":{\"count\":1,\"avgDuration\":\"0s\"},\"totalPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"}},\"server\":{\"uptime\":5992503019270,\"version\":\"DEVELOPMENT.GOGET\",\"commitID\":\"DEVELOPMENT.GOGET\",\"region\":\"\",\"sqsARN\":[]}}}]"
68+
77 erasureJSON = "[{\"error\":\"\",\"addr\":\"192.168.1.218:9000\",\"data\":{\"storage\":{\"Used\":83084,\"Backend\":{\"Type\":2,\"OnlineDisks\":4,\"OfflineDisks\":0,\"StandardSCData\":2,\"StandardSCParity\":2,\"RRSCData\":2,\"RRSCParity\":2,\"Sets\":[[{\"uuid\":\"16ec6f2c-9197-4787-904a-36bb2c2683f8\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"4052e086-ef99-4aa5-ae2b-8e27559432f6\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"d0639950-ddd3-45b0-93ca-fd86f5d79f72\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"30ec68c0-37e1-4592-82c1-26b143c0ac10\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]]}},\"network\":{\"transferred\":404,\"received\":0},\"http\":{\"totalHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalGETs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successGETs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"}},\"server\":{\"uptime\":2738903073,\"version\":\"DEVELOPMENT.GOGET\",\"commitID\":\"DEVELOPMENT.GOGET\",\"region\":\"\",\"sqsARN\":[]}}}]"
69 erasureJSON = "[{\"error\":\"\",\"addr\":\"192.168.1.218:9000\",\"data\":{\"storage\":{\"Used\":83084,\"Backend\":{\"Type\":2,\"OnlineDisks\":4,\"OfflineDisks\":0,\"StandardSCData\":2,\"StandardSCParity\":2,\"RRSCData\":2,\"RRSCParity\":2,\"Sets\":[[{\"uuid\":\"16ec6f2c-9197-4787-904a-36bb2c2683f8\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"4052e086-ef99-4aa5-ae2b-8e27559432f6\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"d0639950-ddd3-45b0-93ca-fd86f5d79f72\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"30ec68c0-37e1-4592-82c1-26b143c0ac10\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]]}},\"network\":{\"transferred\":404,\"received\":0},\"http\":{\"totalHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalGETs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successGETs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"}},\"server\":{\"uptime\":2738903073,\"version\":\"DEVELOPMENT.GOGET\",\"commitID\":\"DEVELOPMENT.GOGET\",\"region\":\"\",\"sqsARN\":[]}}}]"
70+
78 invalidJSON = "[{\"error\":\"\",\"addr\":\"192.168.1.218:9000\",\"data\":{\"storage\":{\"Used\":83084,\"Backend\":{\"Type\":42,\"OnlineDisks\":4,\"OfflineDisks\":0,\"StandardSCData\":2,\"StandardSCParity\":2,\"RRSCData\":2,\"RRSCParity\":2,\"Sets\":[[{\"uuid\":\"16ec6f2c-9197-4787-904a-36bb2c2683f8\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"4052e086-ef99-4aa5-ae2b-8e27559432f6\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"d0639950-ddd3-45b0-93ca-fd86f5d79f72\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"30ec68c0-37e1-4592-82c1-26b143c0ac10\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]]}},\"network\":{\"transferred\":404,\"received\":0},\"http\":{\"totalHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalGETs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successGETs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"}},\"server\":{\"uptime\":2738903073,\"version\":\"DEVELOPMENT.GOGET\",\"commitID\":\"DEVELOPMENT.GOGET\",\"region\":\"\",\"sqsARN\":[]}}}]"
71 invalidJSON = "[{\"error\":\"\",\"addr\":\"192.168.1.218:9000\",\"data\":{\"storage\":{\"Used\":83084,\"Backend\":{\"Type\":42,\"OnlineDisks\":4,\"OfflineDisks\":0,\"StandardSCData\":2,\"StandardSCParity\":2,\"RRSCData\":2,\"RRSCParity\":2,\"Sets\":[[{\"uuid\":\"16ec6f2c-9197-4787-904a-36bb2c2683f8\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"4052e086-ef99-4aa5-ae2b-8e27559432f6\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"d0639950-ddd3-45b0-93ca-fd86f5d79f72\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"30ec68c0-37e1-4592-82c1-26b143c0ac10\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]]}},\"network\":{\"transferred\":404,\"received\":0},\"http\":{\"totalHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successHEADs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalGETs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successGETs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPUTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successPOSTs\":{\"count\":0,\"avgDuration\":\"0s\"},\"totalDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"},\"successDELETEs\":{\"count\":0,\"avgDuration\":\"0s\"}},\"server\":{\"uptime\":2738903073,\"version\":\"DEVELOPMENT.GOGET\",\"commitID\":\"DEVELOPMENT.GOGET\",\"region\":\"\",\"sqsARN\":[]}}}]"
79
72
80parseHealStatusTest :: TestTree
73parseHealStatusTest :: TestTree
81−parseHealStatusTest =
74+parseHealStatusTest = testGroup "Parse MinIO Admin API HealStatus JSON test" $
82− testGroup "Parse MinIO Admin API HealStatus JSON test" $
75+ map (\(tName, tDesc, tfn, tVal) -> testCase tName $ assertBool tDesc $
83− map
76+ tfn (eitherDecode tVal :: Either [Char] HealStatus)) testCases
84− ( \(tName, tDesc, tfn, tVal) ->
77+
85− testCase tName $
86− assertBool tDesc $
87− tfn (eitherDecode tVal :: Either [Char] HealStatus)
88− )
89− testCases
90 where
78 where
91− testCases =
79+ testCases = [ ("Good", "Verify heal result item for erasure backend", isRight, erasureJSON')
92− [ ("Good", "Verify heal result item for erasure backend", isRight, erasureJSON'),
80+ , ("Corrupted", "Verify heal result item for erasure backend", isLeft, invalidJSON')
93− ("Corrupted", "Verify heal result item for erasure backend", isLeft, invalidJSON'),
81+ , ("Incorrect Value", "Verify heal result item for erasure backend", isLeft, invalidItemType)
94− ("Incorrect Value", "Verify heal result item for erasure backend", isLeft, invalidItemType)
82+ ]
95− ]
83+
96 erasureJSON' = "{\"Summary\":\"finished\",\"StartTime\":\"2018-06-05T08:09:47.644465513Z\",\"NumDisks\":4,\"Settings\":{\"recursive\":false,\"dryRun\":false},\"Items\":[{\"resultId\":1,\"type\":\"metadata\",\"bucket\":\"\",\"object\":\"\",\"detail\":\"disk-format\",\"diskCount\":4,\"setCount\":1,\"before\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"after\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"objectSize\":0}]}"
84 erasureJSON' = "{\"Summary\":\"finished\",\"StartTime\":\"2018-06-05T08:09:47.644465513Z\",\"NumDisks\":4,\"Settings\":{\"recursive\":false,\"dryRun\":false},\"Items\":[{\"resultId\":1,\"type\":\"metadata\",\"bucket\":\"\",\"object\":\"\",\"detail\":\"disk-format\",\"diskCount\":4,\"setCount\":1,\"before\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"after\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"objectSize\":0}]}"
85+
97 invalidJSON' = "{\"Summary\":\"finished\",\"StartTime\":\"2018-06-05T08:09:47.644465513Z\",\"NumDisks\":4,\"Settings\":{\"recursive\":false,\"dryRun\":false},\"Items\":[{\"resultId\":1,\"type\":\"metadata\",\"bucket\":\"\",\"object\":\"\",\"detail\":\"disk-format\",\"diskCount\":4,\"setCount\":1,\"before\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"after\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"objectSize\":0}]"
86 invalidJSON' = "{\"Summary\":\"finished\",\"StartTime\":\"2018-06-05T08:09:47.644465513Z\",\"NumDisks\":4,\"Settings\":{\"recursive\":false,\"dryRun\":false},\"Items\":[{\"resultId\":1,\"type\":\"metadata\",\"bucket\":\"\",\"object\":\"\",\"detail\":\"disk-format\",\"diskCount\":4,\"setCount\":1,\"before\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"after\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"objectSize\":0}]"
87+
98 invalidItemType = "{\"Summary\":\"finished\",\"StartTime\":\"2018-06-05T08:09:47.644465513Z\",\"NumDisks\":4,\"Settings\":{\"recursive\":false,\"dryRun\":false},\"Items\":[{\"resultId\":1,\"type\":\"hello\",\"bucket\":\"\",\"object\":\"\",\"detail\":\"disk-format\",\"diskCount\":4,\"setCount\":1,\"before\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"after\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"objectSize\":0}]}"
88 invalidItemType = "{\"Summary\":\"finished\",\"StartTime\":\"2018-06-05T08:09:47.644465513Z\",\"NumDisks\":4,\"Settings\":{\"recursive\":false,\"dryRun\":false},\"Items\":[{\"resultId\":1,\"type\":\"hello\",\"bucket\":\"\",\"object\":\"\",\"detail\":\"disk-format\",\"diskCount\":4,\"setCount\":1,\"before\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"after\":{\"drives\":[{\"uuid\":\"c3487166-b8a4-481a-b1e7-fb9b249e2500\",\"endpoint\":\"/tmp/1\",\"state\":\"ok\"},{\"uuid\":\"55a6e787-184f-4e4c-bf09-03dcada658a9\",\"endpoint\":\"/tmp/2\",\"state\":\"ok\"},{\"uuid\":\"f035d8c3-fca1-4407-b89c-38c2bcf4a641\",\"endpoint\":\"/tmp/3\",\"state\":\"ok\"},{\"uuid\":\"4f8b79d3-db90-4c1d-87c2-35a28b0d9a13\",\"endpoint\":\"/tmp/4\",\"state\":\"ok\"}]},\"objectSize\":0}]}"
99
89
100parseHealStartRespTest :: TestTree
90parseHealStartRespTest :: TestTree
101−parseHealStartRespTest =
91+parseHealStartRespTest = testGroup "Parse MinIO Admin API HealStartResp JSON test" $
102− testGroup "Parse MinIO Admin API HealStartResp JSON test" $
92+ map (\(tName, tDesc, tfn, tVal) -> testCase tName $ assertBool tDesc $
103− map
93+ tfn (eitherDecode tVal :: Either [Char] HealStartResp)) testCases
104− ( \(tName, tDesc, tfn, tVal) ->
94+
105− testCase tName $
106− assertBool tDesc $
107− tfn (eitherDecode tVal :: Either [Char] HealStartResp)
108− )
109− testCases
110 where
95 where
111− testCases =
96+ testCases = [ ("Good", "Verify heal start response for erasure backend", isRight, hsrJSON)
112− [ ("Good", "Verify heal start response for erasure backend", isRight, hsrJSON),
97+ , ("Missing Token", "Verify heal start response for erasure backend", isLeft, missingTokenJSON)
113− ("Missing Token", "Verify heal start response for erasure backend", isLeft, missingTokenJSON)
98+ ]
114− ]
99+
115 hsrJSON = "{\"clientToken\":\"3a3aca49-77dd-4b78-bba7-0978f119b23e\",\"clientAddress\":\"127.0.0.1\",\"startTime\":\"2018-06-05T08:09:47.644394493Z\"}"
100 hsrJSON = "{\"clientToken\":\"3a3aca49-77dd-4b78-bba7-0978f119b23e\",\"clientAddress\":\"127.0.0.1\",\"startTime\":\"2018-06-05T08:09:47.644394493Z\"}"
101+
116 missingTokenJSON = "{\"clientAddress\":\"127.0.0.1\",\"startTime\":\"2018-06-05T08:09:47.644394493Z\"}"
102 missingTokenJSON = "{\"clientAddress\":\"127.0.0.1\",\"startTime\":\"2018-06-05T08:09:47.644394493Z\"}"
test/Network/Minio/JsonParser/Test.hs+27−26
@@ -15,26 +15,26 @@
15--
15--
16
16
17module Network.Minio.JsonParser.Test
17module Network.Minio.JsonParser.Test
18− ( jsonParserTests,
18+ (
19− )
19+ jsonParserTests
20−where
20+ ) where
21
21
22−import Lib.Prelude
22+import Test.Tasty
23−import Network.Minio.Errors
23+import Test.Tasty.HUnit
24−import Network.Minio.JsonParser
24+import UnliftIO (MonadUnliftIO)
25−import Test.Tasty
25+
26−import Test.Tasty.HUnit
26+import Lib.Prelude
27−import UnliftIO (MonadUnliftIO)
27+
28+import Network.Minio.Errors
29+import Network.Minio.JsonParser
28
30
29jsonParserTests :: TestTree
31jsonParserTests :: TestTree
30−jsonParserTests =
32+jsonParserTests = testGroup "JSON Parser Tests"
31− testGroup
33+ [ testCase "Test parseErrResponseJSON" testParseErrResponseJSON
32− "JSON Parser Tests"
34+ ]
33− [ testCase "Test parseErrResponseJSON" testParseErrResponseJSON
34− ]
35
35
36tryValidationErr :: (MonadUnliftIO m) => m a -> m (Either MErrV a)
36tryValidationErr :: (MonadUnliftIO m) => m a -> m (Either MErrV a)
37−tryValidationErr = try
37+tryValidationErr act = try act
38
38
39assertValidationErr :: MErrV -> Assertion
39assertValidationErr :: MErrV -> Assertion
40assertValidationErr e = assertFailure $ "Failed due to validation error => " ++ show e
40assertValidationErr e = assertFailure $ "Failed due to validation error => " ++ show e
@@ -44,20 +44,21 @@ testParseErrResponseJSON = do
44 -- 1. Test parsing of an invalid error json.
44 -- 1. Test parsing of an invalid error json.
45 parseResE <- tryValidationErr $ parseErrResponseJSON "ClearlyInvalidJSON"
45 parseResE <- tryValidationErr $ parseErrResponseJSON "ClearlyInvalidJSON"
46 when (isRight parseResE) $
46 when (isRight parseResE) $
47− assertFailure $
47+ assertFailure $ "Parsing should have failed => " ++ show parseResE
48− "Parsing should have failed => " ++ show parseResE
49
48
50 forM_ cases $ \(jsondata, sErr) -> do
49 forM_ cases $ \(jsondata, sErr) -> do
51 parseErr <- tryValidationErr $ parseErrResponseJSON jsondata
50 parseErr <- tryValidationErr $ parseErrResponseJSON jsondata
52 either assertValidationErr (@?= sErr) parseErr
51 either assertValidationErr (@?= sErr) parseErr
52+
53 where
53 where
54− cases =
54+ cases = [
55− [ -- 2. Test parsing of a valid error json.
55+ -- 2. Test parsing of a valid error json.
56− ( "{\"Code\":\"InvalidAccessKeyId\",\"Message\":\"The access key ID you provided does not exist in our records.\",\"Key\":\"\",\"BucketName\":\"\",\"Resource\":\"/minio/admin/v1/info\",\"RequestId\":\"3L137\",\"HostId\":\"3L137\"}",
56+ ("{\"Code\":\"InvalidAccessKeyId\",\"Message\":\"The access key ID you provided does not exist in our records.\",\"Key\":\"\",\"BucketName\":\"\",\"Resource\":\"/minio/admin/v1/info\",\"RequestId\":\"3L137\",\"HostId\":\"3L137\"}",
57− ServiceErr "InvalidAccessKeyId" "The access key ID you provided does not exist in our records."
57+ ServiceErr "InvalidAccessKeyId" "The access key ID you provided does not exist in our records."
58− ),
58+ )
59− -- 3. Test parsing of a valid, empty Resource.
59+ ,
60− ( "{\"Code\":\"SignatureDoesNotMatch\",\"Message\":\"The request signature we calculated does not match the signature you provided. Check your key and signing method.\",\"Key\":\"\",\"BucketName\":\"\",\"Resource\":\"/minio/admin/v1/info\",\"RequestId\":\"3L137\",\"HostId\":\"3L137\"}",
60+ -- 3. Test parsing of a valid, empty Resource.
61− ServiceErr "SignatureDoesNotMatch" "The request signature we calculated does not match the signature you provided. Check your key and signing method."
61+ ("{\"Code\":\"SignatureDoesNotMatch\",\"Message\":\"The request signature we calculated does not match the signature you provided. Check your key and signing method.\",\"Key\":\"\",\"BucketName\":\"\",\"Resource\":\"/minio/admin/v1/info\",\"RequestId\":\"3L137\",\"HostId\":\"3L137\"}",
62− )
62+ ServiceErr "SignatureDoesNotMatch" "The request signature we calculated does not match the signature you provided. Check your key and signing method."
63+ )
63 ]
64 ]
test/Network/Minio/TestHelpers.hs+8−8
@@ -15,18 +15,18 @@
15--
15--
16
16
17module Network.Minio.TestHelpers
17module Network.Minio.TestHelpers
18− ( runTestNS,
18+ ( runTestNS
19− )
19+ ) where
20−where
21
20
22−import Network.Minio.Data
21+import Network.Minio.Data
23
22
24−newtype TestNS = TestNS {testNamespace :: Text}
23+import Lib.Prelude
24+
25+newtype TestNS = TestNS { testNamespace :: Text }
25
26
26instance HasSvcNamespace TestNS where
27instance HasSvcNamespace TestNS where
27 getSvcNamespace = testNamespace
28 getSvcNamespace = testNamespace
28
29
29runTestNS :: ReaderT TestNS m a -> m a
30runTestNS :: ReaderT TestNS m a -> m a
30−runTestNS =
31+runTestNS = flip runReaderT $
31− flip runReaderT $
32+ TestNS "http://s3.amazonaws.com/doc/2006-03-01/"
32− TestNS "http://s3.amazonaws.com/doc/2006-03-01/"
test/Network/Minio/Utils/Test.hs+17−15
@@ -15,31 +15,33 @@
15--
15--
16
16
17module Network.Minio.Utils.Test
17module Network.Minio.Utils.Test
18− ( limitedMapConcurrentlyTests,
18+ (
19− )
19+ limitedMapConcurrentlyTests
20−where
20+ ) where
21
21
22−import Network.Minio.Utils
22+import Test.Tasty
23−import Test.Tasty
23+import Test.Tasty.HUnit
24−import Test.Tasty.HUnit
24+
25+import Lib.Prelude
26+
27+import Network.Minio.Utils
25
28
26limitedMapConcurrentlyTests :: TestTree
29limitedMapConcurrentlyTests :: TestTree
27−limitedMapConcurrentlyTests =
30+limitedMapConcurrentlyTests = testGroup "limitedMapConcurrently Tests"
28− testGroup
31+ [ testCase "Test with various thread counts" testLMC
29− "limitedMapConcurrently Tests"
32+ ]
30− [ testCase "Test with various thread counts" testLMC
31− ]
32
33
33testLMC :: Assertion
34testLMC :: Assertion
34testLMC = do
35testLMC = do
35 let maxNum = 50
36 let maxNum = 50
36 -- test with thread count of 1 to 2*maxNum
37 -- test with thread count of 1 to 2*maxNum
37− forM_ [1 .. (2 * maxNum)] $ \threads -> do
38+ forM_ [1..(2*maxNum)] $ \threads -> do
38− res <- limitedMapConcurrently threads compute [1 .. maxNum]
39+ res <- limitedMapConcurrently threads compute [1..maxNum]
39 sum res @?= overallResultCheck maxNum
40 sum res @?= overallResultCheck maxNum
40 where
41 where
41 -- simple function to run in each thread
42 -- simple function to run in each thread
42 compute :: Int -> IO Int
43 compute :: Int -> IO Int
43− compute n = return $ sum [1 .. n]
44+ compute n = return $ sum [1..n]
45+
44 -- function to check overall result
46 -- function to check overall result
45− overallResultCheck n = sum $ map (\t -> (t * (t + 1)) `div` 2) [1 .. n]
47+ overallResultCheck n = sum $ map (\t -> (t * (t+1)) `div` 2) [1..n]
test/Network/Minio/XmlGenerator/Test.hs+98−137
@@ -13,33 +13,30 @@
13-- See the License for the specific language governing permissions and
13-- See the License for the specific language governing permissions and
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16−{-# LANGUAGE QuasiQuotes #-}
17
16
17+{-# LANGUAGE QuasiQuotes #-}
18module Network.Minio.XmlGenerator.Test
18module Network.Minio.XmlGenerator.Test
19− ( xmlGeneratorTests,
19+ ( xmlGeneratorTests
20− )
20+ ) where
21−where
21+
22+import Test.Tasty
23+import Test.Tasty.HUnit
24+import Text.RawString.QQ (r)
22
25
23−import qualified Data.ByteString.Lazy as LBS
26+import Lib.Prelude
24−import Lib.Prelude
27+
25−import Network.Minio.Data
28+import Network.Minio.Data
26−import Network.Minio.TestHelpers
29+import Network.Minio.TestHelpers
27−import Network.Minio.XmlGenerator
30+import Network.Minio.XmlGenerator
28−import Network.Minio.XmlParser (parseNotification)
31+import Network.Minio.XmlParser (parseNotification)
29−import Test.Tasty
30−import Test.Tasty.HUnit
31−import Text.RawString.QQ (r)
32−import Text.XML (def, parseLBS)
33
32
34xmlGeneratorTests :: TestTree
33xmlGeneratorTests :: TestTree
35−xmlGeneratorTests =
34+xmlGeneratorTests = testGroup "XML Generator Tests"
36− testGroup
35+ [ testCase "Test mkCreateBucketConfig" testMkCreateBucketConfig
37− "XML Generator Tests"
36+ , testCase "Test mkCompleteMultipartUploadRequest" testMkCompleteMultipartUploadRequest
38− [ testCase "Test mkCreateBucketConfig" testMkCreateBucketConfig,
37+ , testCase "Test mkPutNotificationRequest" testMkPutNotificationRequest
39− testCase "Test mkCompleteMultipartUploadRequest" testMkCompleteMultipartUploadRequest,
38+ , testCase "Test mkSelectRequest" testMkSelectRequest
40− testCase "Test mkPutNotificationRequest" testMkPutNotificationRequest,
39+ ]
41− testCase "Test mkSelectRequest" testMkSelectRequest
42− ]
43
40
44testMkCreateBucketConfig :: Assertion
41testMkCreateBucketConfig :: Assertion
45testMkCreateBucketConfig = do
42testMkCreateBucketConfig = do
@@ -47,136 +44,100 @@ testMkCreateBucketConfig = do
47 assertEqual "CreateBucketConfiguration xml should match: " expected $
44 assertEqual "CreateBucketConfiguration xml should match: " expected $
48 mkCreateBucketConfig ns "EU"
45 mkCreateBucketConfig ns "EU"
49 where
46 where
50− expected =
47+ expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
51− "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
48+ \<CreateBucketConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
52− \<CreateBucketConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
49+ \<LocationConstraint>EU</LocationConstraint>\
53− \<LocationConstraint>EU</LocationConstraint>\
50+ \</CreateBucketConfiguration>"
54− \</CreateBucketConfiguration>"
55
51
56testMkCompleteMultipartUploadRequest :: Assertion
52testMkCompleteMultipartUploadRequest :: Assertion
57testMkCompleteMultipartUploadRequest =
53testMkCompleteMultipartUploadRequest =
58 assertEqual "completeMultipartUpload xml should match: " expected $
54 assertEqual "completeMultipartUpload xml should match: " expected $
59− mkCompleteMultipartUploadRequest [(1, "abc")]
55+ mkCompleteMultipartUploadRequest [(1, "abc")]
60 where
56 where
61− expected =
57+ expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
62− "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
58+ \<CompleteMultipartUpload>\
63− \<CompleteMultipartUpload>\
59+ \<Part>\
64− \<Part>\
60+ \<PartNumber>1</PartNumber><ETag>abc</ETag>\
65− \<PartNumber>1</PartNumber><ETag>abc</ETag>\
61+ \</Part>\
66− \</Part>\
62+ \</CompleteMultipartUpload>"
67− \</CompleteMultipartUpload>"
63+
68
64
69testMkPutNotificationRequest :: Assertion
65testMkPutNotificationRequest :: Assertion
70testMkPutNotificationRequest =
66testMkPutNotificationRequest =
71 forM_ cases $ \val -> do
67 forM_ cases $ \val -> do
72 let ns = "http://s3.amazonaws.com/doc/2006-03-01/"
68 let ns = "http://s3.amazonaws.com/doc/2006-03-01/"
73− result = fromStrictBS $ mkPutNotificationRequest ns val
69+ result = toS $ mkPutNotificationRequest ns val
74 ntf <- runExceptT $ runTestNS $ parseNotification result
70 ntf <- runExceptT $ runTestNS $ parseNotification result
75− either
71+ either (\_ -> assertFailure "XML Parse Error!")
76− (\_ -> assertFailure "XML Parse Error!")
72+ (@?= val) ntf
77− (@?= val)
78− ntf
79 where
73 where
80− cases =
74+ cases = [ Notification []
81− [ Notification
75+ [ NotificationConfig
82− []
76+ "YjVkM2Y0YmUtNGI3NC00ZjQyLWEwNGItNDIyYWUxY2I0N2M4"
83− [ NotificationConfig
77+ "arn:aws:sns:us-east-1:account-id:s3notificationtopic2"
84− "YjVkM2Y0YmUtNGI3NC00ZjQyLWEwNGItNDIyYWUxY2I0N2M4"
78+ [ReducedRedundancyLostObject, ObjectCreated] defaultFilter
85− "arn:aws:sns:us-east-1:account-id:s3notificationtopic2"
79+ ]
86− [ReducedRedundancyLostObject, ObjectCreated]
80+ []
87− defaultFilter
81+ , Notification
88− ]
82+ [ NotificationConfig
89− [],
83+ "1" "arn:aws:sqs:us-west-2:444455556666:s3notificationqueue"
90− Notification
84+ [ObjectCreatedPut]
91− [ NotificationConfig
85+ (Filter $ FilterKey $ FilterRules
92− "1"
86+ [ FilterRule "prefix" "images/"
93− "arn:aws:sqs:us-west-2:444455556666:s3notificationqueue"
87+ , FilterRule "suffix" ".jpg"])
94− [ObjectCreatedPut]
88+ , NotificationConfig
95− ( Filter $
89+ "" "arn:aws:sqs:us-east-1:356671443308:s3notificationqueue"
96− FilterKey $
90+ [ObjectCreated] defaultFilter
97− FilterRules
91+ ]
98− [ FilterRule "prefix" "images/",
92+ [ NotificationConfig
99− FilterRule "suffix" ".jpg"
93+ "" "arn:aws:sns:us-east-1:356671443308:s3notificationtopic2"
100− ]
94+ [ReducedRedundancyLostObject] defaultFilter
101− ),
95+ ]
102− NotificationConfig
96+ [ NotificationConfig
103− ""
97+ "ObjectCreatedEvents" "arn:aws:lambda:us-west-2:35667example:function:CreateThumbnail"
104− "arn:aws:sqs:us-east-1:356671443308:s3notificationqueue"
98+ [ObjectCreated] defaultFilter
105− [ObjectCreated]
99+ ]
106− defaultFilter
100+ ]
107− ]
108− [ NotificationConfig
109− ""
110− "arn:aws:sns:us-east-1:356671443308:s3notificationtopic2"
111− [ReducedRedundancyLostObject]
112− defaultFilter
113− ]
114− [ NotificationConfig
115− "ObjectCreatedEvents"
116− "arn:aws:lambda:us-west-2:35667example:function:CreateThumbnail"
117− [ObjectCreated]
118− defaultFilter
119− ]
120− ]
121
101
122testMkSelectRequest :: Assertion
102testMkSelectRequest :: Assertion
123testMkSelectRequest = mapM_ assertFn cases
103testMkSelectRequest = mapM_ assertFn cases
124 where
104 where
125− assertFn (a, b) =
105+ assertFn (a, b) = assertEqual "selectRequest XML should match: " b $ mkSelectRequest a
126− let generatedReqDoc = parseLBS def $ LBS.fromStrict $ mkSelectRequest a
106+ cases = [ ( SelectRequest "Select * from S3Object" SQL
127− expectedReqDoc = parseLBS def $ LBS.fromStrict b
107+ (InputSerialization (Just CompressionTypeGzip)
128− in case (generatedReqDoc, expectedReqDoc) of
108+ (InputFormatCSV $ fileHeaderInfo FileHeaderIgnore
129− (Right genDoc, Right expDoc) -> assertEqual "selectRequest XML should match: " expDoc genDoc
109+ <> recordDelimiter "\n"
130− (Left err, _) -> assertFailure $ "Generated selectRequest failed to parse as XML" ++ show err
110+ <> fieldDelimiter ","
131− (_, Left err) -> assertFailure $ "Expected selectRequest failed to parse as XML" ++ show err
111+ <> quoteCharacter "\""
132− cases =
112+ <> quoteEscapeCharacter "\""
133− [ ( SelectRequest
113+ ))
134− "Select * from S3Object"
114+ (OutputSerializationCSV $ quoteFields QuoteFieldsAsNeeded
135− SQL
115+ <> recordDelimiter "\n"
136− ( InputSerialization
116+ <> fieldDelimiter ","
137− (Just CompressionTypeGzip)
117+ <> quoteCharacter "\""
138− ( InputFormatCSV $
118+ <> quoteEscapeCharacter "\""
139− fileHeaderInfo FileHeaderIgnore
140− <> recordDelimiter "\n"
141− <> fieldDelimiter ","
142− <> quoteCharacter "\""
143− <> quoteEscapeCharacter "\""
144 )
119 )
145− )
120+ (Just False)
146− ( OutputSerializationCSV $
121+ , [r|<?xml version="1.0" encoding="UTF-8"?><SelectRequest><Expression>Select * from S3Object</Expression><ExpressionType>SQL</ExpressionType><InputSerialization><CompressionType>GZIP</CompressionType><CSV><QuoteCharacter>&#34;</QuoteCharacter><RecordDelimiter>
147− quoteFields QuoteFieldsAsNeeded
122+</RecordDelimiter><FileHeaderInfo>IGNORE</FileHeaderInfo><QuoteEscapeCharacter>&#34;</QuoteEscapeCharacter><FieldDelimiter>,</FieldDelimiter></CSV></InputSerialization><OutputSerialization><CSV><QuoteCharacter>&#34;</QuoteCharacter><QuoteFields>ASNEEDED</QuoteFields><RecordDelimiter>
148− <> recordDelimiter "\n"
123+</RecordDelimiter><QuoteEscapeCharacter>&#34;</QuoteEscapeCharacter><FieldDelimiter>,</FieldDelimiter></CSV></OutputSerialization><RequestProgress><Enabled>FALSE</Enabled></RequestProgress></SelectRequest>|]
149− <> fieldDelimiter ","
124+ )
150− <> quoteCharacter "\""
125+ , ( setRequestProgressEnabled False $
151− <> quoteEscapeCharacter "\""
126+ setInputCompressionType CompressionTypeGzip $
152− )
127+ selectRequest "Select * from S3Object" documentJsonInput
153− (Just False),
128+ (outputJSONFromRecordDelimiter "\n")
154− [r|<?xml version="1.0" encoding="UTF-8"?><SelectRequest><Expression>Select * from S3Object</Expression><ExpressionType>SQL</ExpressionType><InputSerialization><CompressionType>GZIP</CompressionType><CSV><FieldDelimiter>,</FieldDelimiter><FileHeaderInfo>IGNORE</FileHeaderInfo><QuoteCharacter>"</QuoteCharacter><QuoteEscapeCharacter>"</QuoteEscapeCharacter><RecordDelimiter>
129+ , [r|<?xml version="1.0" encoding="UTF-8"?><SelectRequest><Expression>Select * from S3Object</Expression><ExpressionType>SQL</ExpressionType><InputSerialization><CompressionType>GZIP</CompressionType><JSON><Type>DOCUMENT</Type></JSON></InputSerialization><OutputSerialization><JSON><RecordDelimiter>
155−</RecordDelimiter></CSV></InputSerialization><OutputSerialization><CSV><FieldDelimiter>,</FieldDelimiter><QuoteCharacter>"</QuoteCharacter><QuoteEscapeCharacter>"</QuoteEscapeCharacter><QuoteFields>ASNEEDED</QuoteFields><RecordDelimiter>
156−</RecordDelimiter></CSV></OutputSerialization><RequestProgress><Enabled>FALSE</Enabled></RequestProgress></SelectRequest>|]
157− ),
158− ( setRequestProgressEnabled False $
159− setInputCompressionType CompressionTypeGzip $
160− selectRequest
161− "Select * from S3Object"
162− documentJsonInput
163− (outputJSONFromRecordDelimiter "\n"),
164− [r|<?xml version="1.0" encoding="UTF-8"?><SelectRequest><Expression>Select * from S3Object</Expression><ExpressionType>SQL</ExpressionType><InputSerialization><CompressionType>GZIP</CompressionType><JSON><Type>DOCUMENT</Type></JSON></InputSerialization><OutputSerialization><JSON><RecordDelimiter>
165</RecordDelimiter></JSON></OutputSerialization><RequestProgress><Enabled>FALSE</Enabled></RequestProgress></SelectRequest>|]
130</RecordDelimiter></JSON></OutputSerialization><RequestProgress><Enabled>FALSE</Enabled></RequestProgress></SelectRequest>|]
166− ),
131+ )
167− ( setRequestProgressEnabled False $
132+ , ( setRequestProgressEnabled False $
168− setInputCompressionType CompressionTypeNone $
133+ setInputCompressionType CompressionTypeNone $
169− selectRequest
134+ selectRequest "Select * from S3Object" defaultParquetInput
170− "Select * from S3Object"
135+ (outputCSVFromProps $ quoteFields QuoteFieldsAsNeeded
171− defaultParquetInput
136+ <> recordDelimiter "\n"
172− ( outputCSVFromProps $
137+ <> fieldDelimiter ","
173− quoteFields QuoteFieldsAsNeeded
138+ <> quoteCharacter "\""
174− <> recordDelimiter "\n"
139+ <> quoteEscapeCharacter "\"")
175− <> fieldDelimiter ","
140+ , [r|<?xml version="1.0" encoding="UTF-8"?><SelectRequest><Expression>Select * from S3Object</Expression><ExpressionType>SQL</ExpressionType><InputSerialization><CompressionType>NONE</CompressionType><Parquet/></InputSerialization><OutputSerialization><CSV><QuoteCharacter>&#34;</QuoteCharacter><QuoteFields>ASNEEDED</QuoteFields><RecordDelimiter>
176− <> quoteCharacter "\""
141+</RecordDelimiter><QuoteEscapeCharacter>&#34;</QuoteEscapeCharacter><FieldDelimiter>,</FieldDelimiter></CSV></OutputSerialization><RequestProgress><Enabled>FALSE</Enabled></RequestProgress></SelectRequest>|]
177− <> quoteEscapeCharacter "\""
142+ )
178− ),
143+ ]
179− [r|<?xml version="1.0" encoding="UTF-8"?><SelectRequest><Expression>Select * from S3Object</Expression><ExpressionType>SQL</ExpressionType><InputSerialization><CompressionType>NONE</CompressionType><Parquet/></InputSerialization><OutputSerialization><CSV><FieldDelimiter>,</FieldDelimiter><QuoteCharacter>"</QuoteCharacter><QuoteEscapeCharacter>"</QuoteEscapeCharacter><QuoteFields>ASNEEDED</QuoteFields><RecordDelimiter>
180−</RecordDelimiter></CSV></OutputSerialization><RequestProgress><Enabled>FALSE</Enabled></RequestProgress></SelectRequest>|]
181− )
182− ]
test/Network/Minio/XmlParser/Test.hs+276−298
@@ -13,49 +13,48 @@
13-- See the License for the specific language governing permissions and
13-- See the License for the specific language governing permissions and
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16−{-# LANGUAGE QuasiQuotes #-}
17
16
17+{-# LANGUAGE QuasiQuotes #-}
18module Network.Minio.XmlParser.Test
18module Network.Minio.XmlParser.Test
19− ( xmlParserTests,
19+ ( xmlParserTests
20− )
20+ ) where
21−where
21+
22−
22+import qualified Data.HashMap.Strict as H
23−import qualified Data.HashMap.Strict as H
23+import Data.Time (fromGregorian)
24−import Data.Time (fromGregorian)
24+import Test.Tasty
25−import Lib.Prelude
25+import Test.Tasty.HUnit
26−import Network.Minio.Data
26+import Text.RawString.QQ (r)
27−import Network.Minio.Errors
27+import UnliftIO (MonadUnliftIO)
28−import Network.Minio.TestHelpers
28+
29−import Network.Minio.XmlParser
29+import Lib.Prelude
30−import Test.Tasty
30+
31−import Test.Tasty.HUnit
31+import Network.Minio.Data
32−import Text.RawString.QQ (r)
32+import Network.Minio.Errors
33−import UnliftIO (MonadUnliftIO)
33+import Network.Minio.TestHelpers
34+import Network.Minio.XmlParser
34
35
35xmlParserTests :: TestTree
36xmlParserTests :: TestTree
36−xmlParserTests =
37+xmlParserTests = testGroup "XML Parser Tests"
37− testGroup
38+ [ testCase "Test parseLocation" testParseLocation
38− "XML Parser Tests"
39+ , testCase "Test parseNewMultipartUpload" testParseNewMultipartUpload
39− [ testCase "Test parseLocation" testParseLocation,
40+ , testCase "Test parseListObjectsResponse" testParseListObjectsResult
40− testCase "Test parseNewMultipartUpload" testParseNewMultipartUpload,
41+ , testCase "Test parseListObjectsV1Response" testParseListObjectsV1Result
41− testCase "Test parseListObjectsResponse" testParseListObjectsResult,
42+ , testCase "Test parseListUploadsresponse" testParseListIncompleteUploads
42− testCase "Test parseListObjectsV1Response" testParseListObjectsV1Result,
43+ , testCase "Test parseCompleteMultipartUploadResponse" testParseCompleteMultipartUploadResponse
43− testCase "Test parseListUploadsresponse" testParseListIncompleteUploads,
44+ , testCase "Test parseListPartsResponse" testParseListPartsResponse
44− testCase "Test parseCompleteMultipartUploadResponse" testParseCompleteMultipartUploadResponse,
45+ , testCase "Test parseCopyObjectResponse" testParseCopyObjectResponse
45− testCase "Test parseListPartsResponse" testParseListPartsResponse,
46+ , testCase "Test parseNotification" testParseNotification
46− testCase "Test parseCopyObjectResponse" testParseCopyObjectResponse,
47+ , testCase "Test parseSelectProgress" testParseSelectProgress
47− testCase "Test parseNotification" testParseNotification,
48+ ]
48− testCase "Test parseSelectProgress" testParseSelectProgress
49− ]
50
49
51tryValidationErr :: (MonadUnliftIO m) => m a -> m (Either MErrV a)
50tryValidationErr :: (MonadUnliftIO m) => m a -> m (Either MErrV a)
52−tryValidationErr = try
51+tryValidationErr act = try act
53
52
54assertValidtionErr :: MErrV -> Assertion
53assertValidtionErr :: MErrV -> Assertion
55assertValidtionErr e = assertFailure $ "Failed due to validation error => " ++ show e
54assertValidtionErr e = assertFailure $ "Failed due to validation error => " ++ show e
56
55
57eitherValidationErr :: Either MErrV a -> (a -> Assertion) -> Assertion
56eitherValidationErr :: Either MErrV a -> (a -> Assertion) -> Assertion
58−eitherValidationErr (Left e) _ = assertValidtionErr e
57+eitherValidationErr (Left e) _ = assertValidtionErr e
59eitherValidationErr (Right a) f = f a
58eitherValidationErr (Right a) f = f a
60
59
61testParseLocation :: Assertion
60testParseLocation :: Assertion
@@ -63,224 +62,224 @@ testParseLocation = do
63 -- 1. Test parsing of an invalid location constraint xml.
62 -- 1. Test parsing of an invalid location constraint xml.
64 parseResE <- tryValidationErr $ parseLocation "ClearlyInvalidXml"
63 parseResE <- tryValidationErr $ parseLocation "ClearlyInvalidXml"
65 when (isRight parseResE) $
64 when (isRight parseResE) $
66− assertFailure $
65+ assertFailure $ "Parsing should have failed => " ++ show parseResE
67− "Parsing should have failed => " ++ show parseResE
68
66
69 forM_ cases $ \(xmldata, expectedLocation) -> do
67 forM_ cases $ \(xmldata, expectedLocation) -> do
70 parseLocE <- tryValidationErr $ parseLocation xmldata
68 parseLocE <- tryValidationErr $ parseLocation xmldata
71 either assertValidtionErr (@?= expectedLocation) parseLocE
69 either assertValidtionErr (@?= expectedLocation) parseLocE
72 where
70 where
73− cases =
71+ cases = [
74− [ -- 2. Test parsing of a valid location xml.
72+ -- 2. Test parsing of a valid location xml.
75− ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
73+ ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
76− \<LocationConstraint xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">EU</LocationConstraint>",
74+ \<LocationConstraint xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">EU</LocationConstraint>",
77− "EU"
75+ "EU"
78− ),
76+ )
79− -- 3. Test parsing of a valid, empty location xml.
77+ ,
80− ( "<LocationConstraint xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"/>",
78+ -- 3. Test parsing of a valid, empty location xml.
81− "us-east-1"
79+ ("<LocationConstraint xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"/>",
82− )
80+ "us-east-1"
81+ )
83 ]
82 ]
84
83
84+
85testParseNewMultipartUpload :: Assertion
85testParseNewMultipartUpload :: Assertion
86testParseNewMultipartUpload = do
86testParseNewMultipartUpload = do
87 forM_ cases $ \(xmldata, expectedUploadId) -> do
87 forM_ cases $ \(xmldata, expectedUploadId) -> do
88 parsedUploadIdE <- tryValidationErr $ runTestNS $ parseNewMultipartUpload xmldata
88 parsedUploadIdE <- tryValidationErr $ runTestNS $ parseNewMultipartUpload xmldata
89 eitherValidationErr parsedUploadIdE (@?= expectedUploadId)
89 eitherValidationErr parsedUploadIdE (@?= expectedUploadId)
90 where
90 where
91− cases =
91+ cases = [
92− [ ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
92+ ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
93− \<InitiateMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
93+ \<InitiateMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
94− \ <Bucket>example-bucket</Bucket>\
94+ \ <Bucket>example-bucket</Bucket>\
95− \ <Key>example-object</Key>\
95+ \ <Key>example-object</Key>\
96− \ <UploadId>VXBsb2FkIElEIGZvciA2aWWpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA</UploadId>\
96+ \ <UploadId>VXBsb2FkIElEIGZvciA2aWWpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA</UploadId>\
97− \</InitiateMultipartUploadResult>",
97+ \</InitiateMultipartUploadResult>",
98− "VXBsb2FkIElEIGZvciA2aWWpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA"
98+ "VXBsb2FkIElEIGZvciA2aWWpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA"
99− ),
99+ ),
100− ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
100+ ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
101− \<InitiateMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
101+ \<InitiateMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
102− \ <Bucket>example-bucket</Bucket>\
102+ \ <Bucket>example-bucket</Bucket>\
103− \ <Key>example-object</Key>\
103+ \ <Key>example-object</Key>\
104− \ <UploadId>EXAMPLEJZ6e0YupT2h66iePQCc9IEbYbDUy4RTpMeoSMLPRp8Z5o1u8feSRonpvnWsKKG35tI2LB9VDPiCgTy.Gq2VxQLYjrue4Nq.NBdqI-</UploadId>\
104+ \ <UploadId>EXAMPLEJZ6e0YupT2h66iePQCc9IEbYbDUy4RTpMeoSMLPRp8Z5o1u8feSRonpvnWsKKG35tI2LB9VDPiCgTy.Gq2VxQLYjrue4Nq.NBdqI-</UploadId>\
105− \</InitiateMultipartUploadResult>",
105+ \</InitiateMultipartUploadResult>",
106− "EXAMPLEJZ6e0YupT2h66iePQCc9IEbYbDUy4RTpMeoSMLPRp8Z5o1u8feSRonpvnWsKKG35tI2LB9VDPiCgTy.Gq2VxQLYjrue4Nq.NBdqI-"
106+ "EXAMPLEJZ6e0YupT2h66iePQCc9IEbYbDUy4RTpMeoSMLPRp8Z5o1u8feSRonpvnWsKKG35tI2LB9VDPiCgTy.Gq2VxQLYjrue4Nq.NBdqI-"
107− )
107+ )
108 ]
108 ]
109
109
110testParseListObjectsResult :: Assertion
110testParseListObjectsResult :: Assertion
111testParseListObjectsResult = do
111testParseListObjectsResult = do
112− let xmldata =
112+ let
113− "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
113+ xmldata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
114− \<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
114+ \<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
115− \<Name>bucket</Name>\
115+ \<Name>bucket</Name>\
116− \<Prefix/>\
116+ \<Prefix/>\
117− \<NextContinuationToken>opaque</NextContinuationToken>\
117+ \<NextContinuationToken>opaque</NextContinuationToken>\
118− \<KeyCount>1000</KeyCount>\
118+ \<KeyCount>1000</KeyCount>\
119− \<MaxKeys>1000</MaxKeys>\
119+ \<MaxKeys>1000</MaxKeys>\
120− \<IsTruncated>true</IsTruncated>\
120+ \<IsTruncated>true</IsTruncated>\
121− \<Contents>\
121+ \<Contents>\
122− \<Key>my-image.jpg</Key>\
122+ \<Key>my-image.jpg</Key>\
123− \<LastModified>2009-10-12T17:50:30.000Z</LastModified>\
123+ \<LastModified>2009-10-12T17:50:30.000Z</LastModified>\
124− \<ETag>&quot;fba9dede5f27731c9771645a39863328&quot;</ETag>\
124+ \<ETag>&quot;fba9dede5f27731c9771645a39863328&quot;</ETag>\
125− \<Size>434234</Size>\
125+ \<Size>434234</Size>\
126− \<StorageClass>STANDARD</StorageClass>\
126+ \<StorageClass>STANDARD</StorageClass>\
127− \</Contents>\
127+ \</Contents>\
128− \</ListBucketResult>"
128+ \</ListBucketResult>"
129− expectedListResult = ListObjectsResult True (Just "opaque") [object1] []
129+
130− object1 = ObjectInfo "my-image.jpg" modifiedTime1 "\"fba9dede5f27731c9771645a39863328\"" 434234 H.empty H.empty
130+ expectedListResult = ListObjectsResult True (Just "opaque") [object1] []
131− modifiedTime1 = flip UTCTime 64230 $ fromGregorian 2009 10 12
131+ object1 = ObjectInfo "my-image.jpg" modifiedTime1 "\"fba9dede5f27731c9771645a39863328\"" 434234 H.empty H.empty
132+ modifiedTime1 = flip UTCTime 64230 $ fromGregorian 2009 10 12
132
133
133 parsedListObjectsResult <- tryValidationErr $ runTestNS $ parseListObjectsResponse xmldata
134 parsedListObjectsResult <- tryValidationErr $ runTestNS $ parseListObjectsResponse xmldata
134 eitherValidationErr parsedListObjectsResult (@?= expectedListResult)
135 eitherValidationErr parsedListObjectsResult (@?= expectedListResult)
135
136
136testParseListObjectsV1Result :: Assertion
137testParseListObjectsV1Result :: Assertion
137testParseListObjectsV1Result = do
138testParseListObjectsV1Result = do
138− let xmldata =
139+ let
139− "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
140+ xmldata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
140− \<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
141+ \<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
141− \<Name>bucket</Name>\
142+ \<Name>bucket</Name>\
142− \<Prefix/>\
143+ \<Prefix/>\
143− \<NextMarker>my-image1.jpg</NextMarker>\
144+ \<NextMarker>my-image1.jpg</NextMarker>\
144− \<KeyCount>1000</KeyCount>\
145+ \<KeyCount>1000</KeyCount>\
145− \<MaxKeys>1000</MaxKeys>\
146+ \<MaxKeys>1000</MaxKeys>\
146− \<IsTruncated>true</IsTruncated>\
147+ \<IsTruncated>true</IsTruncated>\
147− \<Contents>\
148+ \<Contents>\
148− \<Key>my-image.jpg</Key>\
149+ \<Key>my-image.jpg</Key>\
149− \<LastModified>2009-10-12T17:50:30.000Z</LastModified>\
150+ \<LastModified>2009-10-12T17:50:30.000Z</LastModified>\
150− \<ETag>&quot;fba9dede5f27731c9771645a39863328&quot;</ETag>\
151+ \<ETag>&quot;fba9dede5f27731c9771645a39863328&quot;</ETag>\
151− \<Size>434234</Size>\
152+ \<Size>434234</Size>\
152− \<StorageClass>STANDARD</StorageClass>\
153+ \<StorageClass>STANDARD</StorageClass>\
153− \</Contents>\
154+ \</Contents>\
154− \</ListBucketResult>"
155+ \</ListBucketResult>"
155− expectedListResult = ListObjectsV1Result True (Just "my-image1.jpg") [object1] []
156+
156− object1 = ObjectInfo "my-image.jpg" modifiedTime1 "\"fba9dede5f27731c9771645a39863328\"" 434234 H.empty H.empty
157+ expectedListResult = ListObjectsV1Result True (Just "my-image1.jpg") [object1] []
157− modifiedTime1 = flip UTCTime 64230 $ fromGregorian 2009 10 12
158+ object1 = ObjectInfo "my-image.jpg" modifiedTime1 "\"fba9dede5f27731c9771645a39863328\"" 434234 H.empty H.empty
159+ modifiedTime1 = flip UTCTime 64230 $ fromGregorian 2009 10 12
158
160
159 parsedListObjectsV1Result <- tryValidationErr $ runTestNS $ parseListObjectsV1Response xmldata
161 parsedListObjectsV1Result <- tryValidationErr $ runTestNS $ parseListObjectsV1Response xmldata
160 eitherValidationErr parsedListObjectsV1Result (@?= expectedListResult)
162 eitherValidationErr parsedListObjectsV1Result (@?= expectedListResult)
161
163
162testParseListIncompleteUploads :: Assertion
164testParseListIncompleteUploads :: Assertion
163testParseListIncompleteUploads = do
165testParseListIncompleteUploads = do
164− let xmldata =
166+ let
165− "<ListMultipartUploadsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
167+ xmldata = "<ListMultipartUploadsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
166− \<Bucket>example-bucket</Bucket>\
168+ \<Bucket>example-bucket</Bucket>\
167− \<KeyMarker/>\
169+ \<KeyMarker/>\
168− \<UploadIdMarker/>\
170+ \<UploadIdMarker/>\
169− \<NextKeyMarker>sample.jpg</NextKeyMarker>\
171+ \<NextKeyMarker>sample.jpg</NextKeyMarker>\
170− \<NextUploadIdMarker>Xgw4MJT6ZPAVxpY0SAuGN7q4uWJJM22ZYg1W99trdp4tpO88.PT6.MhO0w2E17eutfAvQfQWoajgE_W2gpcxQw--</NextUploadIdMarker>\
172+ \<NextUploadIdMarker>Xgw4MJT6ZPAVxpY0SAuGN7q4uWJJM22ZYg1W99trdp4tpO88.PT6.MhO0w2E17eutfAvQfQWoajgE_W2gpcxQw--</NextUploadIdMarker>\
171− \<Delimiter>/</Delimiter>\
173+ \<Delimiter>/</Delimiter>\
172− \<Prefix/>\
174+ \<Prefix/>\
173− \<MaxUploads>1000</MaxUploads>\
175+ \<MaxUploads>1000</MaxUploads>\
174− \<IsTruncated>false</IsTruncated>\
176+ \<IsTruncated>false</IsTruncated>\
175− \<Upload>\
177+ \<Upload>\
176− \<Key>sample.jpg</Key>\
178+ \<Key>sample.jpg</Key>\
177− \<UploadId>Agw4MJT6ZPAVxpY0SAuGN7q4uWJJM22ZYg1N99trdp4tpO88.PT6.MhO0w2E17eutfAvQfQWoajgE_W2gpcxQw--</UploadId>\
179+ \<UploadId>Agw4MJT6ZPAVxpY0SAuGN7q4uWJJM22ZYg1N99trdp4tpO88.PT6.MhO0w2E17eutfAvQfQWoajgE_W2gpcxQw--</UploadId>\
178− \<Initiator>\
180+ \<Initiator>\
179− \<ID>314133b66967d86f031c7249d1d9a80249109428335cd0ef1cdc487b4566cb1b</ID>\
181+ \<ID>314133b66967d86f031c7249d1d9a80249109428335cd0ef1cdc487b4566cb1b</ID>\
180− \<DisplayName>s3-nickname</DisplayName>\
182+ \<DisplayName>s3-nickname</DisplayName>\
181− \</Initiator>\
183+ \</Initiator>\
182− \<Owner>\
184+ \<Owner>\
183− \<ID>314133b66967d86f031c7249d1d9a80249109428335cd0ef1cdc487b4566cb1b</ID>\
185+ \<ID>314133b66967d86f031c7249d1d9a80249109428335cd0ef1cdc487b4566cb1b</ID>\
184− \<DisplayName>s3-nickname</DisplayName>\
186+ \<DisplayName>s3-nickname</DisplayName>\
185− \</Owner>\
187+ \</Owner>\
186− \<StorageClass>STANDARD</StorageClass>\
188+ \<StorageClass>STANDARD</StorageClass>\
187− \<Initiated>2010-11-26T19:24:17.000Z</Initiated>\
189+ \<Initiated>2010-11-26T19:24:17.000Z</Initiated>\
188− \</Upload>\
190+ \</Upload>\
189− \<CommonPrefixes>\
191+ \<CommonPrefixes>\
190− \<Prefix>photos/</Prefix>\
192+ \<Prefix>photos/</Prefix>\
191− \</CommonPrefixes>\
193+ \</CommonPrefixes>\
192− \<CommonPrefixes>\
194+ \<CommonPrefixes>\
193− \<Prefix>videos/</Prefix>\
195+ \<Prefix>videos/</Prefix>\
194− \</CommonPrefixes>\
196+ \</CommonPrefixes>\
195− \</ListMultipartUploadsResult>"
197+ \</ListMultipartUploadsResult>"
196− expectedListResult = ListUploadsResult False (Just "sample.jpg") (Just "Xgw4MJT6ZPAVxpY0SAuGN7q4uWJJM22ZYg1W99trdp4tpO88.PT6.MhO0w2E17eutfAvQfQWoajgE_W2gpcxQw--") uploads prefixes
198+ expectedListResult = ListUploadsResult False (Just "sample.jpg") (Just "Xgw4MJT6ZPAVxpY0SAuGN7q4uWJJM22ZYg1W99trdp4tpO88.PT6.MhO0w2E17eutfAvQfQWoajgE_W2gpcxQw--") uploads prefixes
197− uploads = [("sample.jpg", "Agw4MJT6ZPAVxpY0SAuGN7q4uWJJM22ZYg1N99trdp4tpO88.PT6.MhO0w2E17eutfAvQfQWoajgE_W2gpcxQw--", initTime)]
199+ uploads = [("sample.jpg", "Agw4MJT6ZPAVxpY0SAuGN7q4uWJJM22ZYg1N99trdp4tpO88.PT6.MhO0w2E17eutfAvQfQWoajgE_W2gpcxQw--", initTime)]
198− initTime = UTCTime (fromGregorian 2010 11 26) 69857
200+ initTime = UTCTime (fromGregorian 2010 11 26) 69857
199− prefixes = ["photos/", "videos/"]
201+ prefixes = ["photos/", "videos/"]
200
202
201 parsedListUploadsResult <- tryValidationErr $ runTestNS $ parseListUploadsResponse xmldata
203 parsedListUploadsResult <- tryValidationErr $ runTestNS $ parseListUploadsResponse xmldata
202 eitherValidationErr parsedListUploadsResult (@?= expectedListResult)
204 eitherValidationErr parsedListUploadsResult (@?= expectedListResult)
203
205
206+
204testParseCompleteMultipartUploadResponse :: Assertion
207testParseCompleteMultipartUploadResponse :: Assertion
205testParseCompleteMultipartUploadResponse = do
208testParseCompleteMultipartUploadResponse = do
206− let xmldata =
209+ let
207− "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
210+ xmldata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
208− \<CompleteMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
211+\<CompleteMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
209− \<Location>http://Example-Bucket.s3.amazonaws.com/Example-Object</Location>\
212+ \<Location>http://Example-Bucket.s3.amazonaws.com/Example-Object</Location>\
210− \<Bucket>Example-Bucket</Bucket>\
213+ \<Bucket>Example-Bucket</Bucket>\
211− \<Key>Example-Object</Key>\
214+ \<Key>Example-Object</Key>\
212− \<ETag>\"3858f62230ac3c915f300c664312c11f-9\"</ETag>\
215+ \<ETag>\"3858f62230ac3c915f300c664312c11f-9\"</ETag>\
213− \</CompleteMultipartUploadResult>"
216+\</CompleteMultipartUploadResult>"
214− expectedETag = "\"3858f62230ac3c915f300c664312c11f-9\""
217+ expectedETag = "\"3858f62230ac3c915f300c664312c11f-9\""
215
218
216 parsedETagE <- runExceptT $ runTestNS $ parseCompleteMultipartUploadResponse xmldata
219 parsedETagE <- runExceptT $ runTestNS $ parseCompleteMultipartUploadResponse xmldata
217 eitherValidationErr parsedETagE (@?= expectedETag)
220 eitherValidationErr parsedETagE (@?= expectedETag)
218
221
219testParseListPartsResponse :: Assertion
222testParseListPartsResponse :: Assertion
220testParseListPartsResponse = do
223testParseListPartsResponse = do
221− let xmldata =
224+ let
222− "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
225+ xmldata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
223− \<ListPartsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
226+\<ListPartsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
224− \<Bucket>example-bucket</Bucket>\
227+ \<Bucket>example-bucket</Bucket>\
225− \<Key>example-object</Key>\
228+ \<Key>example-object</Key>\
226− \<UploadId>XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA</UploadId>\
229+ \<UploadId>XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA</UploadId>\
227− \<Initiator>\
230+ \<Initiator>\
228− \<ID>arn:aws:iam::111122223333:user/some-user-11116a31-17b5-4fb7-9df5-b288870f11xx</ID>\
231+ \<ID>arn:aws:iam::111122223333:user/some-user-11116a31-17b5-4fb7-9df5-b288870f11xx</ID>\
229− \<DisplayName>umat-user-11116a31-17b5-4fb7-9df5-b288870f11xx</DisplayName>\
232+ \<DisplayName>umat-user-11116a31-17b5-4fb7-9df5-b288870f11xx</DisplayName>\
230− \</Initiator>\
233+ \</Initiator>\
231− \<Owner>\
234+ \<Owner>\
232− \<ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>\
235+ \<ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>\
233− \<DisplayName>someName</DisplayName>\
236+ \<DisplayName>someName</DisplayName>\
234− \</Owner>\
237+ \</Owner>\
235− \<StorageClass>STANDARD</StorageClass>\
238+ \<StorageClass>STANDARD</StorageClass>\
236− \<PartNumberMarker>1</PartNumberMarker>\
239+ \<PartNumberMarker>1</PartNumberMarker>\
237− \<NextPartNumberMarker>3</NextPartNumberMarker>\
240+ \<NextPartNumberMarker>3</NextPartNumberMarker>\
238− \<MaxParts>2</MaxParts>\
241+ \<MaxParts>2</MaxParts>\
239− \<IsTruncated>true</IsTruncated>\
242+ \<IsTruncated>true</IsTruncated>\
240− \<Part>\
243+ \<Part>\
241− \<PartNumber>2</PartNumber>\
244+ \<PartNumber>2</PartNumber>\
242− \<LastModified>2010-11-10T20:48:34.000Z</LastModified>\
245+ \<LastModified>2010-11-10T20:48:34.000Z</LastModified>\
243− \<ETag>\"7778aef83f66abc1fa1e8477f296d394\"</ETag>\
246+ \<ETag>\"7778aef83f66abc1fa1e8477f296d394\"</ETag>\
244− \<Size>10485760</Size>\
247+ \<Size>10485760</Size>\
245− \</Part>\
248+ \</Part>\
246− \<Part>\
249+ \<Part>\
247− \<PartNumber>3</PartNumber>\
250+ \<PartNumber>3</PartNumber>\
248− \<LastModified>2010-11-10T20:48:33.000Z</LastModified>\
251+ \<LastModified>2010-11-10T20:48:33.000Z</LastModified>\
249− \<ETag>\"aaaa18db4cc2f85cedef654fccc4a4x8\"</ETag>\
252+ \<ETag>\"aaaa18db4cc2f85cedef654fccc4a4x8\"</ETag>\
250− \<Size>10485760</Size>\
253+ \<Size>10485760</Size>\
251− \</Part>\
254+ \</Part>\
252− \</ListPartsResult>"
255+\</ListPartsResult>"
253− expectedListResult = ListPartsResult True (Just 3) [part1, part2]
256+
254− part1 = ObjectPartInfo 2 "\"7778aef83f66abc1fa1e8477f296d394\"" 10485760 modifiedTime1
257+ expectedListResult = ListPartsResult True (Just 3) [part1, part2]
255− modifiedTime1 = flip UTCTime 74914 $ fromGregorian 2010 11 10
258+ part1 = ObjectPartInfo 2 "\"7778aef83f66abc1fa1e8477f296d394\"" 10485760 modifiedTime1
256− part2 = ObjectPartInfo 3 "\"aaaa18db4cc2f85cedef654fccc4a4x8\"" 10485760 modifiedTime2
259+ modifiedTime1 = flip UTCTime 74914 $ fromGregorian 2010 11 10
257− modifiedTime2 = flip UTCTime 74913 $ fromGregorian 2010 11 10
260+ part2 = ObjectPartInfo 3 "\"aaaa18db4cc2f85cedef654fccc4a4x8\"" 10485760 modifiedTime2
261+ modifiedTime2 = flip UTCTime 74913 $ fromGregorian 2010 11 10
258
262
259 parsedListPartsResult <- runExceptT $ runTestNS $ parseListPartsResponse xmldata
263 parsedListPartsResult <- runExceptT $ runTestNS $ parseListPartsResponse xmldata
260 eitherValidationErr parsedListPartsResult (@?= expectedListResult)
264 eitherValidationErr parsedListPartsResult (@?= expectedListResult)
261
265
262testParseCopyObjectResponse :: Assertion
266testParseCopyObjectResponse :: Assertion
263testParseCopyObjectResponse = do
267testParseCopyObjectResponse = do
264− let cases =
268+ let
265− [ ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
269+ cases = [ ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
266− \<CopyObjectResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
270+\<CopyObjectResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
267− \<LastModified>2009-10-28T22:32:00.000Z</LastModified>\
271+ \<LastModified>2009-10-28T22:32:00.000Z</LastModified>\
268− \<ETag>\"9b2cf535f27731c974343645a3985328\"</ETag>\
272+ \<ETag>\"9b2cf535f27731c974343645a3985328\"</ETag>\
269− \</CopyObjectResult>",
273+\</CopyObjectResult>",
270− ( "\"9b2cf535f27731c974343645a3985328\"",
274+ ("\"9b2cf535f27731c974343645a3985328\"",
271− UTCTime (fromGregorian 2009 10 28) 81120
275+ UTCTime (fromGregorian 2009 10 28) 81120))
272− )
276+ , ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
273− ),
277+\<CopyPartResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
274− ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
278+ \<LastModified>2009-10-28T22:32:00.000Z</LastModified>\
275− \<CopyPartResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
279+ \<ETag>\"9b2cf535f27731c974343645a3985328\"</ETag>\
276− \<LastModified>2009-10-28T22:32:00.000Z</LastModified>\
280+\</CopyPartResult>",
277− \<ETag>\"9b2cf535f27731c974343645a3985328\"</ETag>\
281+ ("\"9b2cf535f27731c974343645a3985328\"",
278− \</CopyPartResult>",
282+ UTCTime (fromGregorian 2009 10 28) 81120))]
279− ( "\"9b2cf535f27731c974343645a3985328\"",
280− UTCTime (fromGregorian 2009 10 28) 81120
281− )
282− )
283− ]
284
283
285 forM_ cases $ \(xmldata, (etag, modTime)) -> do
284 forM_ cases $ \(xmldata, (etag, modTime)) -> do
286 parseResult <- runExceptT $ runTestNS $ parseCopyObjectResponse xmldata
285 parseResult <- runExceptT $ runTestNS $ parseCopyObjectResponse xmldata
@@ -288,89 +287,73 @@ testParseCopyObjectResponse = do
288
287
289testParseNotification :: Assertion
288testParseNotification :: Assertion
290testParseNotification = do
289testParseNotification = do
291− let cases =
290+ let
292− [ ( "<NotificationConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
291+ cases = [ ("<NotificationConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
293− \ <TopicConfiguration>\
292+\ <TopicConfiguration>\
294− \ <Id>YjVkM2Y0YmUtNGI3NC00ZjQyLWEwNGItNDIyYWUxY2I0N2M4</Id>\
293+\ <Id>YjVkM2Y0YmUtNGI3NC00ZjQyLWEwNGItNDIyYWUxY2I0N2M4</Id>\
295− \ <Topic>arn:aws:sns:us-east-1:account-id:s3notificationtopic2</Topic>\
294+\ <Topic>arn:aws:sns:us-east-1:account-id:s3notificationtopic2</Topic>\
296− \ <Event>s3:ReducedRedundancyLostObject</Event>\
295+\ <Event>s3:ReducedRedundancyLostObject</Event>\
297− \ <Event>s3:ObjectCreated:*</Event>\
296+\ <Event>s3:ObjectCreated:*</Event>\
298− \ </TopicConfiguration>\
297+\ </TopicConfiguration>\
299− \</NotificationConfiguration>",
298+\</NotificationConfiguration>",
300− Notification
299+ Notification []
301− []
300+ [ NotificationConfig
302− [ NotificationConfig
303 "YjVkM2Y0YmUtNGI3NC00ZjQyLWEwNGItNDIyYWUxY2I0N2M4"
301 "YjVkM2Y0YmUtNGI3NC00ZjQyLWEwNGItNDIyYWUxY2I0N2M4"
304 "arn:aws:sns:us-east-1:account-id:s3notificationtopic2"
302 "arn:aws:sns:us-east-1:account-id:s3notificationtopic2"
305− [ReducedRedundancyLostObject, ObjectCreated]
303+ [ReducedRedundancyLostObject, ObjectCreated] defaultFilter
306− defaultFilter
304+ ]
307− ]
305+ [])
308− []
306+ , ("<NotificationConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
309− ),
307+\ <CloudFunctionConfiguration>\
310− ( "<NotificationConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\
308+\ <Id>ObjectCreatedEvents</Id>\
311− \ <CloudFunctionConfiguration>\
309+\ <CloudFunction>arn:aws:lambda:us-west-2:35667example:function:CreateThumbnail</CloudFunction>\
312− \ <Id>ObjectCreatedEvents</Id>\
310+\ <Event>s3:ObjectCreated:*</Event>\
313− \ <CloudFunction>arn:aws:lambda:us-west-2:35667example:function:CreateThumbnail</CloudFunction>\
311+\ </CloudFunctionConfiguration>\
314− \ <Event>s3:ObjectCreated:*</Event>\
312+\ <QueueConfiguration>\
315− \ </CloudFunctionConfiguration>\
313+\ <Id>1</Id>\
316− \ <QueueConfiguration>\
314+\ <Filter>\
317− \ <Id>1</Id>\
315+\ <S3Key>\
318− \ <Filter>\
316+\ <FilterRule>\
319− \ <S3Key>\
317+\ <Name>prefix</Name>\
320− \ <FilterRule>\
318+\ <Value>images/</Value>\
321− \ <Name>prefix</Name>\
319+\ </FilterRule>\
322− \ <Value>images/</Value>\
320+\ <FilterRule>\
323− \ </FilterRule>\
321+\ <Name>suffix</Name>\
324− \ <FilterRule>\
322+\ <Value>.jpg</Value>\
325− \ <Name>suffix</Name>\
323+\ </FilterRule>\
326− \ <Value>.jpg</Value>\
324+\ </S3Key>\
327− \ </FilterRule>\
325+\ </Filter>\
328− \ </S3Key>\
326+\ <Queue>arn:aws:sqs:us-west-2:444455556666:s3notificationqueue</Queue>\
329− \ </Filter>\
327+\ <Event>s3:ObjectCreated:Put</Event>\
330− \ <Queue>arn:aws:sqs:us-west-2:444455556666:s3notificationqueue</Queue>\
328+\ </QueueConfiguration>\
331− \ <Event>s3:ObjectCreated:Put</Event>\
329+\ <TopicConfiguration>\
332− \ </QueueConfiguration>\
330+\ <Topic>arn:aws:sns:us-east-1:356671443308:s3notificationtopic2</Topic>\
333− \ <TopicConfiguration>\
331+\ <Event>s3:ReducedRedundancyLostObject</Event>\
334− \ <Topic>arn:aws:sns:us-east-1:356671443308:s3notificationtopic2</Topic>\
332+\ </TopicConfiguration>\
335− \ <Event>s3:ReducedRedundancyLostObject</Event>\
333+\ <QueueConfiguration>\
336− \ </TopicConfiguration>\
334+\ <Queue>arn:aws:sqs:us-east-1:356671443308:s3notificationqueue</Queue>\
337− \ <QueueConfiguration>\
335+\ <Event>s3:ObjectCreated:*</Event>\
338− \ <Queue>arn:aws:sqs:us-east-1:356671443308:s3notificationqueue</Queue>\
336+\ </QueueConfiguration>)\
339− \ <Event>s3:ObjectCreated:*</Event>\
337+\</NotificationConfiguration>",
340− \ </QueueConfiguration>)\
338+ Notification [ NotificationConfig
341− \</NotificationConfiguration>",
339+ "1" "arn:aws:sqs:us-west-2:444455556666:s3notificationqueue"
342− Notification
340+ [ObjectCreatedPut]
343− [ NotificationConfig
341+ (Filter $ FilterKey $ FilterRules
344− "1"
342+ [FilterRule "prefix" "images/",
345− "arn:aws:sqs:us-west-2:444455556666:s3notificationqueue"
343+ FilterRule "suffix" ".jpg"])
346− [ObjectCreatedPut]
344+ , NotificationConfig
347− ( Filter $
345+ "" "arn:aws:sqs:us-east-1:356671443308:s3notificationqueue"
348− FilterKey $
346+ [ObjectCreated] defaultFilter
349− FilterRules
347+ ]
350− [ FilterRule "prefix" "images/",
348+ [ NotificationConfig
351− FilterRule "suffix" ".jpg"
349+ "" "arn:aws:sns:us-east-1:356671443308:s3notificationtopic2"
352− ]
350+ [ReducedRedundancyLostObject] defaultFilter
353− ),
351+ ]
354− NotificationConfig
352+ [ NotificationConfig
355− ""
353+ "ObjectCreatedEvents" "arn:aws:lambda:us-west-2:35667example:function:CreateThumbnail"
356− "arn:aws:sqs:us-east-1:356671443308:s3notificationqueue"
354+ [ObjectCreated] defaultFilter
357− [ObjectCreated]
355+ ])
358− defaultFilter
356+ ]
359− ]
360− [ NotificationConfig
361− ""
362− "arn:aws:sns:us-east-1:356671443308:s3notificationtopic2"
363− [ReducedRedundancyLostObject]
364− defaultFilter
365− ]
366− [ NotificationConfig
367− "ObjectCreatedEvents"
368− "arn:aws:lambda:us-west-2:35667example:function:CreateThumbnail"
369− [ObjectCreated]
370− defaultFilter
371− ]
372− )
373− ]
374
357
375 forM_ cases $ \(xmldata, val) -> do
358 forM_ cases $ \(xmldata, val) -> do
376 result <- runExceptT $ runTestNS $ parseNotification xmldata
359 result <- runExceptT $ runTestNS $ parseNotification xmldata
@@ -379,25 +362,20 @@ testParseNotification = do
379-- | Tests parsing of both progress and stats
362-- | Tests parsing of both progress and stats
380testParseSelectProgress :: Assertion
363testParseSelectProgress :: Assertion
381testParseSelectProgress = do
364testParseSelectProgress = do
382− let cases =
365+ let cases = [ ([r|<?xml version="1.0" encoding="UTF-8"?>
383− [ ( [r|<?xml version="1.0" encoding="UTF-8"?>
384<Progress>
366<Progress>
385 <BytesScanned>512</BytesScanned>
367 <BytesScanned>512</BytesScanned>
386 <BytesProcessed>1024</BytesProcessed>
368 <BytesProcessed>1024</BytesProcessed>
387 <BytesReturned>1024</BytesReturned>
369 <BytesReturned>1024</BytesReturned>
388−</Progress>|],
370+</Progress>|] , Progress 512 1024 1024)
389− Progress 512 1024 1024
371+ , ([r|<?xml version="1.0" encoding="UTF-8"?>
390− ),
391− ( [r|<?xml version="1.0" encoding="UTF-8"?>
392<Stats>
372<Stats>
393 <BytesScanned>512</BytesScanned>
373 <BytesScanned>512</BytesScanned>
394 <BytesProcessed>1024</BytesProcessed>
374 <BytesProcessed>1024</BytesProcessed>
395 <BytesReturned>1024</BytesReturned>
375 <BytesReturned>1024</BytesReturned>
396−</Stats>|],
376+</Stats>|], Progress 512 1024 1024)
397− Progress 512 1024 1024
377+ ]
398− )
378+
399− ]
379+ forM_ cases $ \(xmldata, progress) -> do
400−
380+ result <- runExceptT $ parseSelectProgress xmldata
401− forM_ cases $ \(xmldata, progress) -> do
381+ eitherValidationErr result (@?= progress)
402− result <- runExceptT $ parseSelectProgress xmldata
403− eitherValidationErr result (@?= progress)
test/Spec.hs+82−83
@@ -1,5 +1,5 @@
1--
1--
2−-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
2+-- MinIO Haskell SDK, (C) 2017, 2018 MinIO, Inc.
3--
3--
4-- Licensed under the Apache License, Version 2.0 (the "License");
4-- Licensed under the Apache License, Version 2.0 (the "License");
5-- you may not use this file except in compliance with the License.
5-- you may not use this file except in compliance with the License.
@@ -14,17 +14,21 @@
14-- limitations under the License.
14-- limitations under the License.
15--
15--
16
16
17−import qualified Data.ByteString as B
17+import Test.Tasty
18−import qualified Data.List as L
18+import Test.Tasty.QuickCheck as QC
19−import Lib.Prelude
19+
20−import Network.Minio.API.Test
20+import qualified Data.ByteString as B
21−import Network.Minio.CopyObject
21+import qualified Data.List as L
22−import Network.Minio.Data
22+
23−import Network.Minio.Utils.Test
23+import Lib.Prelude
24−import Network.Minio.XmlGenerator.Test
24+
25−import Network.Minio.XmlParser.Test
25+import Network.Minio.API.Test
26−import Test.Tasty
26+import Network.Minio.CopyObject
27−import Test.Tasty.QuickCheck as QC
27+import Network.Minio.Data
28+import Network.Minio.PutObject
29+import Network.Minio.Utils.Test
30+import Network.Minio.XmlGenerator.Test
31+import Network.Minio.XmlParser.Test
28
32
29main :: IO ()
33main :: IO ()
30main = defaultMain tests
34main = defaultMain tests
@@ -47,87 +51,82 @@ properties = testGroup "Properties" [qcProps] -- [scProps]
47-- ]
51-- ]
48
52
49qcProps :: TestTree
53qcProps :: TestTree
50−qcProps =
54+qcProps = testGroup "(checked by QuickCheck)"
51− testGroup
55+ [ QC.testProperty "selectPartSizes:" $
52− "(checked by QuickCheck)"
56+ \n -> let (pns, offs, sizes) = L.unzip3 (selectPartSizes n)
53− [ QC.testProperty "selectPartSizes:" $
57+
54− \n ->
55− let (pns, offs, sizes) = L.unzip3 (selectPartSizes n)
56 -- check that pns increments from 1.
58 -- check that pns increments from 1.
57− isPNumsAscendingFrom1 = all (uncurry (==)) $ zip pns [1 ..]
59+ isPNumsAscendingFrom1 = all (\(a, b) -> a == b) $ zip pns [1..]
58− consPairs [] = []
60+
59− consPairs [_] = []
61+ consPairs [] = []
60− consPairs (a : (b : c)) = (a, b) : consPairs (b : c)
62+ consPairs [_] = []
63+ consPairs (a:(b:c)) = (a, b):(consPairs (b:c))
64+
61 -- check `offs` is monotonically increasing.
65 -- check `offs` is monotonically increasing.
62− isOffsetsAsc = all (uncurry (<)) $ consPairs offs
66+ isOffsetsAsc = all (\(a, b) -> a < b) $ consPairs offs
67+
63 -- check sizes sums to n.
68 -- check sizes sums to n.
64 isSumSizeOk = sum sizes == n
69 isSumSizeOk = sum sizes == n
70+
65 -- check sizes are constant except last
71 -- check sizes are constant except last
66 isSizesConstantExceptLast =
72 isSizesConstantExceptLast =
67− all (uncurry (==)) (consPairs $ L.init sizes)
73+ all (\(a, b) -> a == b) (consPairs $ L.init sizes)
74+
68 -- check each part except last is at least minPartSize;
75 -- check each part except last is at least minPartSize;
69 -- last part may be 0 only if it is the only part.
76 -- last part may be 0 only if it is the only part.
70 nparts = length sizes
77 nparts = length sizes
71 isMinPartSizeOk =
78 isMinPartSizeOk =
72− if
79+ if | nparts > 1 -> -- last part can be smaller but > 0
73− | nparts > 1 -> -- last part can be smaller but > 0
80+ all (>= minPartSize) (take (nparts - 1) sizes) &&
74− all (>= minPartSize) (take (nparts - 1) sizes)
81+ all (\s -> s > 0) (drop (nparts - 1) sizes)
75− && all (> 0) (drop (nparts - 1) sizes)
82+ | nparts == 1 -> -- size may be 0 here.
76− | nparts == 1 -> -- size may be 0 here.
83+ maybe True (\x -> x >= 0 && x <= minPartSize) $
77− maybe True (\x -> x >= 0 && x <= minPartSize) $
84+ headMay sizes
78− listToMaybe sizes
85+ | otherwise -> False
79− | otherwise -> False
86+
80− in n < 0
87+ in n < 0 ||
81− || ( isPNumsAscendingFrom1
88+ (isPNumsAscendingFrom1 && isOffsetsAsc && isSumSizeOk &&
82− && isOffsetsAsc
89+ isSizesConstantExceptLast && isMinPartSizeOk)
83− && isSumSizeOk
90+
84− && isSizesConstantExceptLast
91+ , QC.testProperty "selectCopyRanges:" $
85− && isMinPartSizeOk
92+ \(start, end) ->
86− ),
93+ let (_, pairs) = L.unzip (selectCopyRanges (start, end))
87− QC.testProperty "selectCopyRanges:" $
94+
88− \(start, end) ->
95+ -- is last part's snd offset end?
89− let (_, pairs) = L.unzip (selectCopyRanges (start, end))
96+ isLastPartOk = maybe False ((end ==) . snd) $ lastMay pairs
90− -- is last part's snd offset end?
97+ -- is first part's fst offset start
91− isLastPartOk = maybe False ((end ==) . snd) $ lastMay pairs
98+ isFirstPartOk = maybe False ((start ==) . fst) $ headMay pairs
92− -- is first part's fst offset start
99+
93− isFirstPartOk = maybe False ((start ==) . fst) $ listToMaybe pairs
100+ -- each pair is >=64MiB except last, and all those parts
94− -- each pair is >=64MiB except last, and all those parts
101+ -- have same size.
95− -- have same size.
102+ initSizes = maybe [] (map (\(a, b) -> b - a + 1)) $ initMay pairs
96− initSizes = maybe [] (map (\(a, b) -> b - a + 1) . init) (nonEmpty pairs)
103+ isPartSizesOk = all (>= minPartSize) initSizes &&
97− isPartSizesOk =
104+ maybe True (\k -> all (== k) initSizes)
98− all (>= minPartSize) initSizes
105+ (headMay initSizes)
99− && maybe
106+
100− True
107+ -- returned offsets are contiguous.
101− (\k -> all (== k) initSizes)
108+ fsts = drop 1 $ map fst pairs
102− (listToMaybe initSizes)
109+ snds = take (length pairs - 1) $ map snd pairs
103− -- returned offsets are contiguous.
110+ isContParts = length fsts == length snds &&
104− fsts = drop 1 $ map fst pairs
111+ and (map (\(a, b) -> a == b + 1) $ zip fsts snds)
105− snds = take (length pairs - 1) $ map snd pairs
112+
106− isContParts =
113+ in start < 0 || start > end ||
107− length fsts == length snds
114+ (isLastPartOk && isFirstPartOk && isPartSizesOk && isContParts)
108− && all (\(a, b) -> a == b + 1) (zip fsts snds)
115+
109− in start < 0
116+ , QC.testProperty "mkSSECKey:" $
110− || start > end
117+ \w8s -> let bs = B.pack w8s
111− || (isLastPartOk && isFirstPartOk && isPartSizesOk && isContParts),
118+ r = mkSSECKey bs
112− QC.testProperty "mkSSECKey:" $
119+ in case r of
113− \w8s ->
120+ Just _ -> B.length bs == 32
114− let bs = B.pack w8s
115− r = mkSSECKey bs
116− in case r of
117− Just _ -> B.length bs == 32
118 Nothing -> B.length bs /= 32
121 Nothing -> B.length bs /= 32
119− ]
122+ ]
120
123
121unitTests :: TestTree
124unitTests :: TestTree
122−unitTests =
125+unitTests = testGroup "Unit tests" [ xmlGeneratorTests, xmlParserTests
123− testGroup
126+ , bucketNameValidityTests
124− "Unit tests"
127+ , objectNameValidityTests
125− [ xmlGeneratorTests,
128+ , parseServerInfoJSONTest
126− xmlParserTests,
129+ , parseHealStatusTest
127− bucketNameValidityTests,
130+ , parseHealStartRespTest
128− objectNameValidityTests,
131+ , limitedMapConcurrentlyTests
129− parseServerInfoJSONTest,
132+ ]
130− parseHealStatusTest,
131− parseHealStartRespTest,
132− limitedMapConcurrentlyTests
133− ]
test/cert/private.keydeleted−28
@@ -1,28 +0,0 @@
1−-----BEGIN PRIVATE KEY-----
2−MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3G9IiC+adjf0p
3−i/2KYc+4dizeuzUFN7wraSdhiOMdQgCnu9Dc3t2YEsQhNdrARjOTyXd36KeM3TwI
4−rPJ61dRGQSuN12l+mzngFJQjE0sysZHUJOLQC3rVvIrHSQ57utPg8ifxt/SunlPY
5−fhcUcq03onMGq44yOfE6mIhoe0Y9wcPQ3RjjNNS44bgmXiXwa+Do0h2hEn6/essq
6−5KjHL8WW2vGg7G9edpYdxINA/A2fdLtr8BwPNrZhOx84eee2XcUNdBuTtUUxE+0L
7−9yRqItqddriRxJFwOXb5OPW8xx2WGaV2a0wbE4gB2PTwwDvfo72mo9HXHZUHM1A8
8−4TD/RXMbAgMBAAECggEBAJ7r1oUWLyGvinn0tijUm6RNbMQjVvEgXoCO008jr3pF
9−PqxVpgEMrOa/4tmwFBus0jcCNF4t3r2zhddBw3I5A/O1vEdvHnBz6NdDBQ8sP6fP
10−1fF50iEe1Y2MBibQkXFxxVMG2QRB1Gt5nuvXA9ELdqtCovK3EsMk5ukkWb/UvjH5
11−8hcmQsaSqvzFEF4wJSY2mkeGSGIJTphPhhuA22xbhaBMInQyhZu8EHsn0h6s/Wgy
12−C4Cp2+4qZTKaaf6x3/ZjJ8CuKiSX+ZsJKjOEv8sqx7j/Y7QFOmJPewInKDhwazr/
13−xIK+N0KXPbUzeSEz6ZvExNDTxtR5ZlQP2UrRDg28yQECgYEA4Is1O2BvKVzNFOkj
14−bTVz25a/bb0Xrcfgi0Y9rdfLzlNdItFjAkxLTVRSW2Hv9ICl0RDDAG+wTlktXRdh
15−rfvDjwG2CvLQo1VEdMWTTkKVg03SwMEy2hFiWV69lENFGSaY8Y6unZDbia5HQinA
16−EgSS4sCojS+a2jtzG5FVVHJDKlkCgYEA0MKhMhD4SUhr2y1idPBrmLxuW5mVozuW
17−8bYaBeSzmfS0BRsN4fP9JGODPBPDdNbfGfGC9ezWLgD/lmCgjIEyBOq8EmqWSsiS
18−Kihds1+Z7hXtbzGsFGAFJJTIh7blBCsK5QFuyuih2UG0fL9z6K/dy+UUJkzrYqph
19−vSfKixyM8pMCgYEAmUPLsNyw4325aeV8TeWnUCJERaZFDFQa21W1cfyS2yEhuEtN
20−llr3JzBACqn9vFk3VU1onNqfb8sE4L696KCpKeqUFEMK0AG6eS4Gzus53Gb5TKJS
21−kHA/PhshsZp9Bp7G1FJ8s4YVo5N2hh2zQVkn3Wh9Y+kzfHQJrK51nO9lEvkCgYBi
22−BuKWle1gzAcJdnhDHRoJMIJJtQbVDYhFnBMALXJAmu1lcFzGe0GlMq1PKqCfXr6I
23−eiXawQmZtJJP1LPPBmOsd2U06KQGHcS00xucvQmVCOrjSdnZ/3SqxsqbH8DOgj+t
24−ZUzXLwHA+N99rJEK9Hob4kfh7ECjpgobPnIXfKKazQKBgQChAuiXHtf/Qq18hY3u
25−x48zFWjGgfd6GpOBZYkXOwGdCJgnYjZbE26LZEnYbwPh8ZUA2vp7mgHRJkD5e3Fj
26−ERuJLCw86WqyYZmLEuBciYGjCZqR5nbavfwsziWD00jeNruds2ZwKxRfFm4V7o2S
27−WLd/RUatd2Uu9f3B2J78OUdnxg==
28−-----END PRIVATE KEY-----
test/cert/public.crtdeleted−19
@@ -1,19 +0,0 @@
1−-----BEGIN CERTIFICATE-----
2−MIIDCzCCAfOgAwIBAgIUaIUOMI78LCu+r1zl0mmFHK8n5/AwDQYJKoZIhvcNAQEL
3−BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE5MTAyNDE5NTMxOVoYDzIxMTkw
4−OTMwMTk1MzE5WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
5−AQUAA4IBDwAwggEKAoIBAQC3G9IiC+adjf0pi/2KYc+4dizeuzUFN7wraSdhiOMd
6−QgCnu9Dc3t2YEsQhNdrARjOTyXd36KeM3TwIrPJ61dRGQSuN12l+mzngFJQjE0sy
7−sZHUJOLQC3rVvIrHSQ57utPg8ifxt/SunlPYfhcUcq03onMGq44yOfE6mIhoe0Y9
8−wcPQ3RjjNNS44bgmXiXwa+Do0h2hEn6/essq5KjHL8WW2vGg7G9edpYdxINA/A2f
9−dLtr8BwPNrZhOx84eee2XcUNdBuTtUUxE+0L9yRqItqddriRxJFwOXb5OPW8xx2W
10−GaV2a0wbE4gB2PTwwDvfo72mo9HXHZUHM1A84TD/RXMbAgMBAAGjUzBRMB0GA1Ud
11−DgQWBBSEWXQ2JRD+OK7/KTmlD+OW16pGmzAfBgNVHSMEGDAWgBSEWXQ2JRD+OK7/
12−KTmlD+OW16pGmzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCF
13−0zYRaabB3X0jzGI9/Lr3Phrb90GvoL1DFLRuiOuTlDkz0vrm/HrZskwHCgMNrkCj
14−OTD9Vpas4D1QZBbQbRzfnf3OOoG4bgmcCwLFZl3dy27yIDAhrmbUP++g9l1Jmy4v
15−vBR/M4lt2scQ8LcZYEPqhEaE5EzFQEjtaxDcKdWDNKY9W1NUzSIABhF9eHiAUNdH
16−AFNJlYeBlCHxcWIeqgon184Dqp/CsvKtz3z3Ni+rlwPM/zuJCFHh1VF+z++0LJjG
17−roBCV0Tro4XyiEz9yp7Cb5kQYMaj1KL9TqBG0tZx0pmv7y+lXc4TT6DEllXz6USy
18−rbIba9/uUet3BqeIMTqj
19−-----END CERTIFICATE-----