diff --git a/go.mod b/go.mod
index 7baf9b1a2..2eb62c80d 100644
--- a/go.mod
+++ b/go.mod
@@ -43,7 +43,7 @@ require (
 	github.com/jackc/pgx/v5 v5.6.0
 	github.com/microcosm-cc/bluemonday v1.0.27
 	github.com/miekg/dns v1.1.61
-	github.com/minio/minio-go/v7 v7.0.72
+	github.com/minio/minio-go/v7 v7.0.73
 	github.com/mitchellh/mapstructure v1.5.0
 	github.com/ncruces/go-sqlite3 v0.17.0
 	github.com/oklog/ulid v1.3.1
@@ -119,6 +119,7 @@ require (
 	github.com/gin-contrib/sse v0.1.0 // indirect
 	github.com/go-errors/errors v1.4.1 // indirect
 	github.com/go-fed/httpsig v1.1.0 // indirect
+	github.com/go-ini/ini v1.67.0 // indirect
 	github.com/go-jose/go-jose/v4 v4.0.1 // indirect
 	github.com/go-logr/logr v1.4.1 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
@@ -137,7 +138,7 @@ require (
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.20.0 // indirect
 	github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 // indirect
-	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/goccy/go-json v0.10.3 // indirect
 	github.com/godbus/dbus/v5 v5.0.4 // indirect
 	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
 	github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
@@ -159,8 +160,8 @@ require (
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
-	github.com/klauspost/compress v1.17.8 // indirect
-	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+	github.com/klauspost/compress v1.17.9 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.8 // indirect
 	github.com/kr/pretty v0.3.1 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
diff --git a/go.sum b/go.sum
index 7c3a7c906..96fa75ee8 100644
--- a/go.sum
+++ b/go.sum
@@ -207,6 +207,8 @@ github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
+github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
 github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -254,8 +256,8 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
 github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 h1:PSPmmucxGiFBtbQcttHTUc4LQ3P09AW+ldO2qspyKdY=
 github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
-github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
-github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
+github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
@@ -391,12 +393,12 @@ github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
-github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
-github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
+github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -422,8 +424,8 @@ github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
 github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
-github.com/minio/minio-go/v7 v7.0.72 h1:ZSbxs2BfJensLyHdVOgHv+pfmvxYraaUy07ER04dWnA=
-github.com/minio/minio-go/v7 v7.0.72/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
+github.com/minio/minio-go/v7 v7.0.73 h1:qr2vi96Qm7kZ4v7LLebjte+MQh621fFWnv93p12htEo=
+github.com/minio/minio-go/v7 v7.0.73/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
diff --git a/vendor/github.com/go-ini/ini/.editorconfig b/vendor/github.com/go-ini/ini/.editorconfig
new file mode 100644
index 000000000..4a2d9180f
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/.editorconfig
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*_test.go]
+trim_trailing_whitespace = false
diff --git a/vendor/github.com/go-ini/ini/.gitignore b/vendor/github.com/go-ini/ini/.gitignore
new file mode 100644
index 000000000..588388bda
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/.gitignore
@@ -0,0 +1,7 @@
+testdata/conf_out.ini
+ini.sublime-project
+ini.sublime-workspace
+testdata/conf_reflect.ini
+.idea
+/.vscode
+.DS_Store
diff --git a/vendor/github.com/go-ini/ini/.golangci.yml b/vendor/github.com/go-ini/ini/.golangci.yml
new file mode 100644
index 000000000..631e36925
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/.golangci.yml
@@ -0,0 +1,27 @@
+linters-settings:
+  staticcheck:
+    checks: [
+      "all",
+      "-SA1019" # There are valid use cases of strings.Title
+    ]
+  nakedret:
+    max-func-lines: 0 # Disallow any unnamed return statement
+
+linters:
+  enable:
+    - deadcode
+    - errcheck
+    - gosimple
+    - govet
+    - ineffassign
+    - staticcheck
+    - structcheck
+    - typecheck
+    - unused
+    - varcheck
+    - nakedret
+    - gofmt
+    - rowserrcheck
+    - unconvert
+    - goimports
+    - unparam
diff --git a/vendor/github.com/go-ini/ini/LICENSE b/vendor/github.com/go-ini/ini/LICENSE
new file mode 100644
index 000000000..d361bbcdf
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright 2014 Unknwon
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/vendor/github.com/go-ini/ini/Makefile b/vendor/github.com/go-ini/ini/Makefile
new file mode 100644
index 000000000..f3b0dae2d
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/Makefile
@@ -0,0 +1,15 @@
+.PHONY: build test bench vet coverage
+
+build: vet bench
+
+test:
+	go test -v -cover -race
+
+bench:
+	go test -v -cover -test.bench=. -test.benchmem
+
+vet:
+	go vet
+
+coverage:
+	go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
diff --git a/vendor/github.com/go-ini/ini/README.md b/vendor/github.com/go-ini/ini/README.md
new file mode 100644
index 000000000..30606d970
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/README.md
@@ -0,0 +1,43 @@
+# INI
+
+[![GitHub Workflow Status](https://img.shields.io/github/checks-status/go-ini/ini/main?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=branch%3Amain)
+[![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini)
+[![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc)
+[![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini)
+
+![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
+
+Package ini provides INI file read and write functionality in Go.
+
+## Features
+
+- Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites.
+- Read with recursion values.
+- Read with parent-child sections.
+- Read with auto-increment key names.
+- Read with multiple-line values.
+- Read with tons of helper methods.
+- Read and convert values to Go types.
+- Read and **WRITE** comments of sections and keys.
+- Manipulate sections, keys and comments with ease.
+- Keep sections and keys in order as you parse and save.
+
+## Installation
+
+The minimum requirement of Go is **1.13**.
+
+```sh
+$ go get gopkg.in/ini.v1
+```
+
+Please add `-u` flag to update in the future.
+
+## Getting Help
+
+- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
+- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
+- 中国大陆镜像:https://ini.unknwon.cn
+
+## License
+
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/vendor/github.com/go-ini/ini/codecov.yml b/vendor/github.com/go-ini/ini/codecov.yml
new file mode 100644
index 000000000..e02ec84bc
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/codecov.yml
@@ -0,0 +1,16 @@
+coverage:
+  range: "60...95"
+  status:
+    project:
+      default:
+        threshold: 1%
+        informational: true
+    patch:
+      defualt:
+        only_pulls: true
+        informational: true
+
+comment:
+  layout: 'diff'
+
+github_checks: false
diff --git a/vendor/github.com/go-ini/ini/data_source.go b/vendor/github.com/go-ini/ini/data_source.go
new file mode 100644
index 000000000..c3a541f1d
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/data_source.go
@@ -0,0 +1,76 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+)
+
+var (
+	_ dataSource = (*sourceFile)(nil)
+	_ dataSource = (*sourceData)(nil)
+	_ dataSource = (*sourceReadCloser)(nil)
+)
+
+// dataSource is an interface that returns object which can be read and closed.
+type dataSource interface {
+	ReadCloser() (io.ReadCloser, error)
+}
+
+// sourceFile represents an object that contains content on the local file system.
+type sourceFile struct {
+	name string
+}
+
+func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
+	return os.Open(s.name)
+}
+
+// sourceData represents an object that contains content in memory.
+type sourceData struct {
+	data []byte
+}
+
+func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
+	return ioutil.NopCloser(bytes.NewReader(s.data)), nil
+}
+
+// sourceReadCloser represents an input stream with Close method.
+type sourceReadCloser struct {
+	reader io.ReadCloser
+}
+
+func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
+	return s.reader, nil
+}
+
+func parseDataSource(source interface{}) (dataSource, error) {
+	switch s := source.(type) {
+	case string:
+		return sourceFile{s}, nil
+	case []byte:
+		return &sourceData{s}, nil
+	case io.ReadCloser:
+		return &sourceReadCloser{s}, nil
+	case io.Reader:
+		return &sourceReadCloser{ioutil.NopCloser(s)}, nil
+	default:
+		return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
+	}
+}
diff --git a/vendor/github.com/go-ini/ini/deprecated.go b/vendor/github.com/go-ini/ini/deprecated.go
new file mode 100644
index 000000000..48b8e66d6
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/deprecated.go
@@ -0,0 +1,22 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+var (
+	// Deprecated: Use "DefaultSection" instead.
+	DEFAULT_SECTION = DefaultSection
+	// Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
+	AllCapsUnderscore = SnackCase
+)
diff --git a/vendor/github.com/go-ini/ini/error.go b/vendor/github.com/go-ini/ini/error.go
new file mode 100644
index 000000000..f66bc94b8
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/error.go
@@ -0,0 +1,49 @@
+// Copyright 2016 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+	"fmt"
+)
+
+// ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one.
+type ErrDelimiterNotFound struct {
+	Line string
+}
+
+// IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound.
+func IsErrDelimiterNotFound(err error) bool {
+	_, ok := err.(ErrDelimiterNotFound)
+	return ok
+}
+
+func (err ErrDelimiterNotFound) Error() string {
+	return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
+}
+
+// ErrEmptyKeyName indicates the error type of no key name is found which there should be one.
+type ErrEmptyKeyName struct {
+	Line string
+}
+
+// IsErrEmptyKeyName returns true if the given error is an instance of ErrEmptyKeyName.
+func IsErrEmptyKeyName(err error) bool {
+	_, ok := err.(ErrEmptyKeyName)
+	return ok
+}
+
+func (err ErrEmptyKeyName) Error() string {
+	return fmt.Sprintf("empty key name: %s", err.Line)
+}
diff --git a/vendor/github.com/go-ini/ini/file.go b/vendor/github.com/go-ini/ini/file.go
new file mode 100644
index 000000000..f8b22408b
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/file.go
@@ -0,0 +1,541 @@
+// Copyright 2017 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"strings"
+	"sync"
+)
+
+// File represents a combination of one or more INI files in memory.
+type File struct {
+	options     LoadOptions
+	dataSources []dataSource
+
+	// Should make things safe, but sometimes doesn't matter.
+	BlockMode bool
+	lock      sync.RWMutex
+
+	// To keep data in order.
+	sectionList []string
+	// To keep track of the index of a section with same name.
+	// This meta list is only used with non-unique section names are allowed.
+	sectionIndexes []int
+
+	// Actual data is stored here.
+	sections map[string][]*Section
+
+	NameMapper
+	ValueMapper
+}
+
+// newFile initializes File object with given data sources.
+func newFile(dataSources []dataSource, opts LoadOptions) *File {
+	if len(opts.KeyValueDelimiters) == 0 {
+		opts.KeyValueDelimiters = "=:"
+	}
+	if len(opts.KeyValueDelimiterOnWrite) == 0 {
+		opts.KeyValueDelimiterOnWrite = "="
+	}
+	if len(opts.ChildSectionDelimiter) == 0 {
+		opts.ChildSectionDelimiter = "."
+	}
+
+	return &File{
+		BlockMode:   true,
+		dataSources: dataSources,
+		sections:    make(map[string][]*Section),
+		options:     opts,
+	}
+}
+
+// Empty returns an empty file object.
+func Empty(opts ...LoadOptions) *File {
+	var opt LoadOptions
+	if len(opts) > 0 {
+		opt = opts[0]
+	}
+
+	// Ignore error here, we are sure our data is good.
+	f, _ := LoadSources(opt, []byte(""))
+	return f
+}
+
+// NewSection creates a new section.
+func (f *File) NewSection(name string) (*Section, error) {
+	if len(name) == 0 {
+		return nil, errors.New("empty section name")
+	}
+
+	if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
+		name = strings.ToLower(name)
+	}
+
+	if f.BlockMode {
+		f.lock.Lock()
+		defer f.lock.Unlock()
+	}
+
+	if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
+		return f.sections[name][0], nil
+	}
+
+	f.sectionList = append(f.sectionList, name)
+
+	// NOTE: Append to indexes must happen before appending to sections,
+	// otherwise index will have off-by-one problem.
+	f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
+
+	sec := newSection(f, name)
+	f.sections[name] = append(f.sections[name], sec)
+
+	return sec, nil
+}
+
+// NewRawSection creates a new section with an unparseable body.
+func (f *File) NewRawSection(name, body string) (*Section, error) {
+	section, err := f.NewSection(name)
+	if err != nil {
+		return nil, err
+	}
+
+	section.isRawSection = true
+	section.rawBody = body
+	return section, nil
+}
+
+// NewSections creates a list of sections.
+func (f *File) NewSections(names ...string) (err error) {
+	for _, name := range names {
+		if _, err = f.NewSection(name); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// GetSection returns section by given name.
+func (f *File) GetSection(name string) (*Section, error) {
+	secs, err := f.SectionsByName(name)
+	if err != nil {
+		return nil, err
+	}
+
+	return secs[0], err
+}
+
+// HasSection returns true if the file contains a section with given name.
+func (f *File) HasSection(name string) bool {
+	section, _ := f.GetSection(name)
+	return section != nil
+}
+
+// SectionsByName returns all sections with given name.
+func (f *File) SectionsByName(name string) ([]*Section, error) {
+	if len(name) == 0 {
+		name = DefaultSection
+	}
+	if f.options.Insensitive || f.options.InsensitiveSections {
+		name = strings.ToLower(name)
+	}
+
+	if f.BlockMode {
+		f.lock.RLock()
+		defer f.lock.RUnlock()
+	}
+
+	secs := f.sections[name]
+	if len(secs) == 0 {
+		return nil, fmt.Errorf("section %q does not exist", name)
+	}
+
+	return secs, nil
+}
+
+// Section assumes named section exists and returns a zero-value when not.
+func (f *File) Section(name string) *Section {
+	sec, err := f.GetSection(name)
+	if err != nil {
+		if name == "" {
+			name = DefaultSection
+		}
+		sec, _ = f.NewSection(name)
+		return sec
+	}
+	return sec
+}
+
+// SectionWithIndex assumes named section exists and returns a new section when not.
+func (f *File) SectionWithIndex(name string, index int) *Section {
+	secs, err := f.SectionsByName(name)
+	if err != nil || len(secs) <= index {
+		// NOTE: It's OK here because the only possible error is empty section name,
+		// but if it's empty, this piece of code won't be executed.
+		newSec, _ := f.NewSection(name)
+		return newSec
+	}
+
+	return secs[index]
+}
+
+// Sections returns a list of Section stored in the current instance.
+func (f *File) Sections() []*Section {
+	if f.BlockMode {
+		f.lock.RLock()
+		defer f.lock.RUnlock()
+	}
+
+	sections := make([]*Section, len(f.sectionList))
+	for i, name := range f.sectionList {
+		sections[i] = f.sections[name][f.sectionIndexes[i]]
+	}
+	return sections
+}
+
+// ChildSections returns a list of child sections of given section name.
+func (f *File) ChildSections(name string) []*Section {
+	return f.Section(name).ChildSections()
+}
+
+// SectionStrings returns list of section names.
+func (f *File) SectionStrings() []string {
+	list := make([]string, len(f.sectionList))
+	copy(list, f.sectionList)
+	return list
+}
+
+// DeleteSection deletes a section or all sections with given name.
+func (f *File) DeleteSection(name string) {
+	secs, err := f.SectionsByName(name)
+	if err != nil {
+		return
+	}
+
+	for i := 0; i < len(secs); i++ {
+		// For non-unique sections, it is always needed to remove the first one so
+		// in the next iteration, the subsequent section continue having index 0.
+		// Ignoring the error as index 0 never returns an error.
+		_ = f.DeleteSectionWithIndex(name, 0)
+	}
+}
+
+// DeleteSectionWithIndex deletes a section with given name and index.
+func (f *File) DeleteSectionWithIndex(name string, index int) error {
+	if !f.options.AllowNonUniqueSections && index != 0 {
+		return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
+	}
+
+	if len(name) == 0 {
+		name = DefaultSection
+	}
+	if f.options.Insensitive || f.options.InsensitiveSections {
+		name = strings.ToLower(name)
+	}
+
+	if f.BlockMode {
+		f.lock.Lock()
+		defer f.lock.Unlock()
+	}
+
+	// Count occurrences of the sections
+	occurrences := 0
+
+	sectionListCopy := make([]string, len(f.sectionList))
+	copy(sectionListCopy, f.sectionList)
+
+	for i, s := range sectionListCopy {
+		if s != name {
+			continue
+		}
+
+		if occurrences == index {
+			if len(f.sections[name]) <= 1 {
+				delete(f.sections, name) // The last one in the map
+			} else {
+				f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
+			}
+
+			// Fix section lists
+			f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
+			f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
+
+		} else if occurrences > index {
+			// Fix the indices of all following sections with this name.
+			f.sectionIndexes[i-1]--
+		}
+
+		occurrences++
+	}
+
+	return nil
+}
+
+func (f *File) reload(s dataSource) error {
+	r, err := s.ReadCloser()
+	if err != nil {
+		return err
+	}
+	defer r.Close()
+
+	return f.parse(r)
+}
+
+// Reload reloads and parses all data sources.
+func (f *File) Reload() (err error) {
+	for _, s := range f.dataSources {
+		if err = f.reload(s); err != nil {
+			// In loose mode, we create an empty default section for nonexistent files.
+			if os.IsNotExist(err) && f.options.Loose {
+				_ = f.parse(bytes.NewBuffer(nil))
+				continue
+			}
+			return err
+		}
+		if f.options.ShortCircuit {
+			return nil
+		}
+	}
+	return nil
+}
+
+// Append appends one or more data sources and reloads automatically.
+func (f *File) Append(source interface{}, others ...interface{}) error {
+	ds, err := parseDataSource(source)
+	if err != nil {
+		return err
+	}
+	f.dataSources = append(f.dataSources, ds)
+	for _, s := range others {
+		ds, err = parseDataSource(s)
+		if err != nil {
+			return err
+		}
+		f.dataSources = append(f.dataSources, ds)
+	}
+	return f.Reload()
+}
+
+func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
+	equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
+
+	if PrettyFormat || PrettyEqual {
+		equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
+	}
+
+	// Use buffer to make sure target is safe until finish encoding.
+	buf := bytes.NewBuffer(nil)
+	lastSectionIdx := len(f.sectionList) - 1
+	for i, sname := range f.sectionList {
+		sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
+		if len(sec.Comment) > 0 {
+			// Support multiline comments
+			lines := strings.Split(sec.Comment, LineBreak)
+			for i := range lines {
+				if lines[i][0] != '#' && lines[i][0] != ';' {
+					lines[i] = "; " + lines[i]
+				} else {
+					lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+				}
+
+				if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+					return nil, err
+				}
+			}
+		}
+
+		if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
+			if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
+				return nil, err
+			}
+		} else {
+			// Write nothing if default section is empty
+			if len(sec.keyList) == 0 {
+				continue
+			}
+		}
+
+		isLastSection := i == lastSectionIdx
+		if sec.isRawSection {
+			if _, err := buf.WriteString(sec.rawBody); err != nil {
+				return nil, err
+			}
+
+			if PrettySection && !isLastSection {
+				// Put a line between sections
+				if _, err := buf.WriteString(LineBreak); err != nil {
+					return nil, err
+				}
+			}
+			continue
+		}
+
+		// Count and generate alignment length and buffer spaces using the
+		// longest key. Keys may be modified if they contain certain characters so
+		// we need to take that into account in our calculation.
+		alignLength := 0
+		if PrettyFormat {
+			for _, kname := range sec.keyList {
+				keyLength := len(kname)
+				// First case will surround key by ` and second by """
+				if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
+					keyLength += 2
+				} else if strings.Contains(kname, "`") {
+					keyLength += 6
+				}
+
+				if keyLength > alignLength {
+					alignLength = keyLength
+				}
+			}
+		}
+		alignSpaces := bytes.Repeat([]byte(" "), alignLength)
+
+	KeyList:
+		for _, kname := range sec.keyList {
+			key := sec.Key(kname)
+			if len(key.Comment) > 0 {
+				if len(indent) > 0 && sname != DefaultSection {
+					buf.WriteString(indent)
+				}
+
+				// Support multiline comments
+				lines := strings.Split(key.Comment, LineBreak)
+				for i := range lines {
+					if lines[i][0] != '#' && lines[i][0] != ';' {
+						lines[i] = "; " + strings.TrimSpace(lines[i])
+					} else {
+						lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+					}
+
+					if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+						return nil, err
+					}
+				}
+			}
+
+			if len(indent) > 0 && sname != DefaultSection {
+				buf.WriteString(indent)
+			}
+
+			switch {
+			case key.isAutoIncrement:
+				kname = "-"
+			case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
+				kname = "`" + kname + "`"
+			case strings.Contains(kname, "`"):
+				kname = `"""` + kname + `"""`
+			}
+
+			writeKeyValue := func(val string) (bool, error) {
+				if _, err := buf.WriteString(kname); err != nil {
+					return false, err
+				}
+
+				if key.isBooleanType {
+					buf.WriteString(LineBreak)
+					return true, nil
+				}
+
+				// Write out alignment spaces before "=" sign
+				if PrettyFormat {
+					buf.Write(alignSpaces[:alignLength-len(kname)])
+				}
+
+				// In case key value contains "\n", "`", "\"", "#" or ";"
+				if strings.ContainsAny(val, "\n`") {
+					val = `"""` + val + `"""`
+				} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
+					val = "`" + val + "`"
+				} else if len(strings.TrimSpace(val)) != len(val) {
+					val = `"` + val + `"`
+				}
+				if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
+					return false, err
+				}
+				return false, nil
+			}
+
+			shadows := key.ValueWithShadows()
+			if len(shadows) == 0 {
+				if _, err := writeKeyValue(""); err != nil {
+					return nil, err
+				}
+			}
+
+			for _, val := range shadows {
+				exitLoop, err := writeKeyValue(val)
+				if err != nil {
+					return nil, err
+				} else if exitLoop {
+					continue KeyList
+				}
+			}
+
+			for _, val := range key.nestedValues {
+				if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
+					return nil, err
+				}
+			}
+		}
+
+		if PrettySection && !isLastSection {
+			// Put a line between sections
+			if _, err := buf.WriteString(LineBreak); err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	return buf, nil
+}
+
+// WriteToIndent writes content into io.Writer with given indention.
+// If PrettyFormat has been set to be true,
+// it will align "=" sign with spaces under each section.
+func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
+	buf, err := f.writeToBuffer(indent)
+	if err != nil {
+		return 0, err
+	}
+	return buf.WriteTo(w)
+}
+
+// WriteTo writes file content into io.Writer.
+func (f *File) WriteTo(w io.Writer) (int64, error) {
+	return f.WriteToIndent(w, "")
+}
+
+// SaveToIndent writes content to file system with given value indention.
+func (f *File) SaveToIndent(filename, indent string) error {
+	// Note: Because we are truncating with os.Create,
+	// 	so it's safer to save to a temporary file location and rename after done.
+	buf, err := f.writeToBuffer(indent)
+	if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(filename, buf.Bytes(), 0666)
+}
+
+// SaveTo writes content to file system.
+func (f *File) SaveTo(filename string) error {
+	return f.SaveToIndent(filename, "")
+}
diff --git a/vendor/github.com/go-ini/ini/helper.go b/vendor/github.com/go-ini/ini/helper.go
new file mode 100644
index 000000000..f9d80a682
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/helper.go
@@ -0,0 +1,24 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+func inSlice(str string, s []string) bool {
+	for _, v := range s {
+		if str == v {
+			return true
+		}
+	}
+	return false
+}
diff --git a/vendor/github.com/go-ini/ini/ini.go b/vendor/github.com/go-ini/ini/ini.go
new file mode 100644
index 000000000..99e7f8651
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/ini.go
@@ -0,0 +1,176 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package ini provides INI file read and write functionality in Go.
+package ini
+
+import (
+	"os"
+	"regexp"
+	"runtime"
+	"strings"
+)
+
+const (
+	// Maximum allowed depth when recursively substituing variable names.
+	depthValues = 99
+)
+
+var (
+	// DefaultSection is the name of default section. You can use this var or the string literal.
+	// In most of cases, an empty string is all you need to access the section.
+	DefaultSection = "DEFAULT"
+
+	// LineBreak is the delimiter to determine or compose a new line.
+	// This variable will be changed to "\r\n" automatically on Windows at package init time.
+	LineBreak = "\n"
+
+	// Variable regexp pattern: %(variable)s
+	varPattern = regexp.MustCompile(`%\(([^)]+)\)s`)
+
+	// DefaultHeader explicitly writes default section header.
+	DefaultHeader = false
+
+	// PrettySection indicates whether to put a line between sections.
+	PrettySection = true
+	// PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output
+	// or reduce all possible spaces for compact format.
+	PrettyFormat = true
+	// PrettyEqual places spaces around "=" sign even when PrettyFormat is false.
+	PrettyEqual = false
+	// DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled.
+	DefaultFormatLeft = ""
+	// DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled.
+	DefaultFormatRight = ""
+)
+
+var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
+
+func init() {
+	if runtime.GOOS == "windows" && !inTest {
+		LineBreak = "\r\n"
+	}
+}
+
+// LoadOptions contains all customized options used for load data source(s).
+type LoadOptions struct {
+	// Loose indicates whether the parser should ignore nonexistent files or return error.
+	Loose bool
+	// Insensitive indicates whether the parser forces all section and key names to lowercase.
+	Insensitive bool
+	// InsensitiveSections indicates whether the parser forces all section to lowercase.
+	InsensitiveSections bool
+	// InsensitiveKeys indicates whether the parser forces all key names to lowercase.
+	InsensitiveKeys bool
+	// IgnoreContinuation indicates whether to ignore continuation lines while parsing.
+	IgnoreContinuation bool
+	// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
+	IgnoreInlineComment bool
+	// SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
+	SkipUnrecognizableLines bool
+	// ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
+	ShortCircuit bool
+	// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
+	// This type of keys are mostly used in my.cnf.
+	AllowBooleanKeys bool
+	// AllowShadows indicates whether to keep track of keys with same name under same section.
+	AllowShadows bool
+	// AllowNestedValues indicates whether to allow AWS-like nested values.
+	// Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
+	AllowNestedValues bool
+	// AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+	// Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+	// Relevant quote:  Values can also span multiple lines, as long as they are indented deeper
+	// than the first line of the value.
+	AllowPythonMultilineValues bool
+	// SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+	// Docs: https://docs.python.org/2/library/configparser.html
+	// Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+	// In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+	SpaceBeforeInlineComment bool
+	// UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
+	// when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
+	UnescapeValueDoubleQuotes bool
+	// UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
+	// when value is NOT surrounded by any quotes.
+	// Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
+	UnescapeValueCommentSymbols bool
+	// UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
+	// conform to key/value pairs. Specify the names of those blocks here.
+	UnparseableSections []string
+	// KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
+	KeyValueDelimiters string
+	// KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
+	KeyValueDelimiterOnWrite string
+	// ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
+	ChildSectionDelimiter string
+	// PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
+	PreserveSurroundedQuote bool
+	// DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).
+	DebugFunc DebugFunc
+	// ReaderBufferSize is the buffer size of the reader in bytes.
+	ReaderBufferSize int
+	// AllowNonUniqueSections indicates whether to allow sections with the same name multiple times.
+	AllowNonUniqueSections bool
+	// AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
+	AllowDuplicateShadowValues bool
+}
+
+// DebugFunc is the type of function called to log parse events.
+type DebugFunc func(message string)
+
+// LoadSources allows caller to apply customized options for loading from data source(s).
+func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
+	sources := make([]dataSource, len(others)+1)
+	sources[0], err = parseDataSource(source)
+	if err != nil {
+		return nil, err
+	}
+	for i := range others {
+		sources[i+1], err = parseDataSource(others[i])
+		if err != nil {
+			return nil, err
+		}
+	}
+	f := newFile(sources, opts)
+	if err = f.Reload(); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// Load loads and parses from INI data sources.
+// Arguments can be mixed of file name with string type, or raw data in []byte.
+// It will return error if list contains nonexistent files.
+func Load(source interface{}, others ...interface{}) (*File, error) {
+	return LoadSources(LoadOptions{}, source, others...)
+}
+
+// LooseLoad has exactly same functionality as Load function
+// except it ignores nonexistent files instead of returning error.
+func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
+	return LoadSources(LoadOptions{Loose: true}, source, others...)
+}
+
+// InsensitiveLoad has exactly same functionality as Load function
+// except it forces all section and key names to be lowercased.
+func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
+	return LoadSources(LoadOptions{Insensitive: true}, source, others...)
+}
+
+// ShadowLoad has exactly same functionality as Load function
+// except it allows have shadow keys.
+func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
+	return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
+}
diff --git a/vendor/github.com/go-ini/ini/key.go b/vendor/github.com/go-ini/ini/key.go
new file mode 100644
index 000000000..a19d9f38e
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/key.go
@@ -0,0 +1,837 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Key represents a key under a section.
+type Key struct {
+	s               *Section
+	Comment         string
+	name            string
+	value           string
+	isAutoIncrement bool
+	isBooleanType   bool
+
+	isShadow bool
+	shadows  []*Key
+
+	nestedValues []string
+}
+
+// newKey simply return a key object with given values.
+func newKey(s *Section, name, val string) *Key {
+	return &Key{
+		s:     s,
+		name:  name,
+		value: val,
+	}
+}
+
+func (k *Key) addShadow(val string) error {
+	if k.isShadow {
+		return errors.New("cannot add shadow to another shadow key")
+	} else if k.isAutoIncrement || k.isBooleanType {
+		return errors.New("cannot add shadow to auto-increment or boolean key")
+	}
+
+	if !k.s.f.options.AllowDuplicateShadowValues {
+		// Deduplicate shadows based on their values.
+		if k.value == val {
+			return nil
+		}
+		for i := range k.shadows {
+			if k.shadows[i].value == val {
+				return nil
+			}
+		}
+	}
+
+	shadow := newKey(k.s, k.name, val)
+	shadow.isShadow = true
+	k.shadows = append(k.shadows, shadow)
+	return nil
+}
+
+// AddShadow adds a new shadow key to itself.
+func (k *Key) AddShadow(val string) error {
+	if !k.s.f.options.AllowShadows {
+		return errors.New("shadow key is not allowed")
+	}
+	return k.addShadow(val)
+}
+
+func (k *Key) addNestedValue(val string) error {
+	if k.isAutoIncrement || k.isBooleanType {
+		return errors.New("cannot add nested value to auto-increment or boolean key")
+	}
+
+	k.nestedValues = append(k.nestedValues, val)
+	return nil
+}
+
+// AddNestedValue adds a nested value to the key.
+func (k *Key) AddNestedValue(val string) error {
+	if !k.s.f.options.AllowNestedValues {
+		return errors.New("nested value is not allowed")
+	}
+	return k.addNestedValue(val)
+}
+
+// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
+type ValueMapper func(string) string
+
+// Name returns name of key.
+func (k *Key) Name() string {
+	return k.name
+}
+
+// Value returns raw value of key for performance purpose.
+func (k *Key) Value() string {
+	return k.value
+}
+
+// ValueWithShadows returns raw values of key and its shadows if any. Shadow
+// keys with empty values are ignored from the returned list.
+func (k *Key) ValueWithShadows() []string {
+	if len(k.shadows) == 0 {
+		if k.value == "" {
+			return []string{}
+		}
+		return []string{k.value}
+	}
+
+	vals := make([]string, 0, len(k.shadows)+1)
+	if k.value != "" {
+		vals = append(vals, k.value)
+	}
+	for _, s := range k.shadows {
+		if s.value != "" {
+			vals = append(vals, s.value)
+		}
+	}
+	return vals
+}
+
+// NestedValues returns nested values stored in the key.
+// It is possible returned value is nil if no nested values stored in the key.
+func (k *Key) NestedValues() []string {
+	return k.nestedValues
+}
+
+// transformValue takes a raw value and transforms to its final string.
+func (k *Key) transformValue(val string) string {
+	if k.s.f.ValueMapper != nil {
+		val = k.s.f.ValueMapper(val)
+	}
+
+	// Fail-fast if no indicate char found for recursive value
+	if !strings.Contains(val, "%") {
+		return val
+	}
+	for i := 0; i < depthValues; i++ {
+		vr := varPattern.FindString(val)
+		if len(vr) == 0 {
+			break
+		}
+
+		// Take off leading '%(' and trailing ')s'.
+		noption := vr[2 : len(vr)-2]
+
+		// Search in the same section.
+		// If not found or found the key itself, then search again in default section.
+		nk, err := k.s.GetKey(noption)
+		if err != nil || k == nk {
+			nk, _ = k.s.f.Section("").GetKey(noption)
+			if nk == nil {
+				// Stop when no results found in the default section,
+				// and returns the value as-is.
+				break
+			}
+		}
+
+		// Substitute by new value and take off leading '%(' and trailing ')s'.
+		val = strings.Replace(val, vr, nk.value, -1)
+	}
+	return val
+}
+
+// String returns string representation of value.
+func (k *Key) String() string {
+	return k.transformValue(k.value)
+}
+
+// Validate accepts a validate function which can
+// return modifed result as key value.
+func (k *Key) Validate(fn func(string) string) string {
+	return fn(k.String())
+}
+
+// parseBool returns the boolean value represented by the string.
+//
+// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
+// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
+// Any other value returns an error.
+func parseBool(str string) (value bool, err error) {
+	switch str {
+	case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
+		return true, nil
+	case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
+		return false, nil
+	}
+	return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
+}
+
+// Bool returns bool type value.
+func (k *Key) Bool() (bool, error) {
+	return parseBool(k.String())
+}
+
+// Float64 returns float64 type value.
+func (k *Key) Float64() (float64, error) {
+	return strconv.ParseFloat(k.String(), 64)
+}
+
+// Int returns int type value.
+func (k *Key) Int() (int, error) {
+	v, err := strconv.ParseInt(k.String(), 0, 64)
+	return int(v), err
+}
+
+// Int64 returns int64 type value.
+func (k *Key) Int64() (int64, error) {
+	return strconv.ParseInt(k.String(), 0, 64)
+}
+
+// Uint returns uint type valued.
+func (k *Key) Uint() (uint, error) {
+	u, e := strconv.ParseUint(k.String(), 0, 64)
+	return uint(u), e
+}
+
+// Uint64 returns uint64 type value.
+func (k *Key) Uint64() (uint64, error) {
+	return strconv.ParseUint(k.String(), 0, 64)
+}
+
+// Duration returns time.Duration type value.
+func (k *Key) Duration() (time.Duration, error) {
+	return time.ParseDuration(k.String())
+}
+
+// TimeFormat parses with given format and returns time.Time type value.
+func (k *Key) TimeFormat(format string) (time.Time, error) {
+	return time.Parse(format, k.String())
+}
+
+// Time parses with RFC3339 format and returns time.Time type value.
+func (k *Key) Time() (time.Time, error) {
+	return k.TimeFormat(time.RFC3339)
+}
+
+// MustString returns default value if key value is empty.
+func (k *Key) MustString(defaultVal string) string {
+	val := k.String()
+	if len(val) == 0 {
+		k.value = defaultVal
+		return defaultVal
+	}
+	return val
+}
+
+// MustBool always returns value without error,
+// it returns false if error occurs.
+func (k *Key) MustBool(defaultVal ...bool) bool {
+	val, err := k.Bool()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatBool(defaultVal[0])
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustFloat64 always returns value without error,
+// it returns 0.0 if error occurs.
+func (k *Key) MustFloat64(defaultVal ...float64) float64 {
+	val, err := k.Float64()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustInt always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt(defaultVal ...int) int {
+	val, err := k.Int()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustInt64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt64(defaultVal ...int64) int64 {
+	val, err := k.Int64()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatInt(defaultVal[0], 10)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustUint always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint(defaultVal ...uint) uint {
+	val, err := k.Uint()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustUint64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
+	val, err := k.Uint64()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatUint(defaultVal[0], 10)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustDuration always returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
+	val, err := k.Duration()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = defaultVal[0].String()
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustTimeFormat always parses with given format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
+	val, err := k.TimeFormat(format)
+	if len(defaultVal) > 0 && err != nil {
+		k.value = defaultVal[0].Format(format)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustTime always parses with RFC3339 format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
+	return k.MustTimeFormat(time.RFC3339, defaultVal...)
+}
+
+// In always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) In(defaultVal string, candidates []string) string {
+	val := k.String()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InFloat64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
+	val := k.MustFloat64()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InInt always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt(defaultVal int, candidates []int) int {
+	val := k.MustInt()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InInt64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
+	val := k.MustInt64()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InUint always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
+	val := k.MustUint()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InUint64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
+	val := k.MustUint64()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InTimeFormat always parses with given format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
+	val := k.MustTimeFormat(format)
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InTime always parses with RFC3339 format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
+	return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
+}
+
+// RangeFloat64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
+	val := k.MustFloat64()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeInt checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt(defaultVal, min, max int) int {
+	val := k.MustInt()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeInt64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
+	val := k.MustInt64()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeTimeFormat checks if value with given format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
+	val := k.MustTimeFormat(format)
+	if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeTime checks if value with RFC3339 format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
+	return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
+}
+
+// Strings returns list of string divided by given delimiter.
+func (k *Key) Strings(delim string) []string {
+	str := k.String()
+	if len(str) == 0 {
+		return []string{}
+	}
+
+	runes := []rune(str)
+	vals := make([]string, 0, 2)
+	var buf bytes.Buffer
+	escape := false
+	idx := 0
+	for {
+		if escape {
+			escape = false
+			if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
+				buf.WriteRune('\\')
+			}
+			buf.WriteRune(runes[idx])
+		} else {
+			if runes[idx] == '\\' {
+				escape = true
+			} else if strings.HasPrefix(string(runes[idx:]), delim) {
+				idx += len(delim) - 1
+				vals = append(vals, strings.TrimSpace(buf.String()))
+				buf.Reset()
+			} else {
+				buf.WriteRune(runes[idx])
+			}
+		}
+		idx++
+		if idx == len(runes) {
+			break
+		}
+	}
+
+	if buf.Len() > 0 {
+		vals = append(vals, strings.TrimSpace(buf.String()))
+	}
+
+	return vals
+}
+
+// StringsWithShadows returns list of string divided by given delimiter.
+// Shadows will also be appended if any.
+func (k *Key) StringsWithShadows(delim string) []string {
+	vals := k.ValueWithShadows()
+	results := make([]string, 0, len(vals)*2)
+	for i := range vals {
+		if len(vals) == 0 {
+			continue
+		}
+
+		results = append(results, strings.Split(vals[i], delim)...)
+	}
+
+	for i := range results {
+		results[i] = k.transformValue(strings.TrimSpace(results[i]))
+	}
+	return results
+}
+
+// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Float64s(delim string) []float64 {
+	vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
+	return vals
+}
+
+// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Ints(delim string) []int {
+	vals, _ := k.parseInts(k.Strings(delim), true, false)
+	return vals
+}
+
+// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Int64s(delim string) []int64 {
+	vals, _ := k.parseInt64s(k.Strings(delim), true, false)
+	return vals
+}
+
+// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Uints(delim string) []uint {
+	vals, _ := k.parseUints(k.Strings(delim), true, false)
+	return vals
+}
+
+// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Uint64s(delim string) []uint64 {
+	vals, _ := k.parseUint64s(k.Strings(delim), true, false)
+	return vals
+}
+
+// Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Bools(delim string) []bool {
+	vals, _ := k.parseBools(k.Strings(delim), true, false)
+	return vals
+}
+
+// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
+// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
+func (k *Key) TimesFormat(format, delim string) []time.Time {
+	vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
+	return vals
+}
+
+// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
+func (k *Key) Times(delim string) []time.Time {
+	return k.TimesFormat(time.RFC3339, delim)
+}
+
+// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
+// it will not be included to result list.
+func (k *Key) ValidFloat64s(delim string) []float64 {
+	vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
+	return vals
+}
+
+// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
+// not be included to result list.
+func (k *Key) ValidInts(delim string) []int {
+	vals, _ := k.parseInts(k.Strings(delim), false, false)
+	return vals
+}
+
+// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
+// then it will not be included to result list.
+func (k *Key) ValidInt64s(delim string) []int64 {
+	vals, _ := k.parseInt64s(k.Strings(delim), false, false)
+	return vals
+}
+
+// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
+// then it will not be included to result list.
+func (k *Key) ValidUints(delim string) []uint {
+	vals, _ := k.parseUints(k.Strings(delim), false, false)
+	return vals
+}
+
+// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
+// integer, then it will not be included to result list.
+func (k *Key) ValidUint64s(delim string) []uint64 {
+	vals, _ := k.parseUint64s(k.Strings(delim), false, false)
+	return vals
+}
+
+// ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned
+// integer, then it will not be included to result list.
+func (k *Key) ValidBools(delim string) []bool {
+	vals, _ := k.parseBools(k.Strings(delim), false, false)
+	return vals
+}
+
+// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
+	vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
+	return vals
+}
+
+// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimes(delim string) []time.Time {
+	return k.ValidTimesFormat(time.RFC3339, delim)
+}
+
+// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
+	return k.parseFloat64s(k.Strings(delim), false, true)
+}
+
+// StrictInts returns list of int divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInts(delim string) ([]int, error) {
+	return k.parseInts(k.Strings(delim), false, true)
+}
+
+// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInt64s(delim string) ([]int64, error) {
+	return k.parseInt64s(k.Strings(delim), false, true)
+}
+
+// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUints(delim string) ([]uint, error) {
+	return k.parseUints(k.Strings(delim), false, true)
+}
+
+// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
+	return k.parseUint64s(k.Strings(delim), false, true)
+}
+
+// StrictBools returns list of bool divided by given delimiter or error on first invalid input.
+func (k *Key) StrictBools(delim string) ([]bool, error) {
+	return k.parseBools(k.Strings(delim), false, true)
+}
+
+// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
+	return k.parseTimesFormat(format, k.Strings(delim), false, true)
+}
+
+// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
+	return k.StrictTimesFormat(time.RFC3339, delim)
+}
+
+// parseBools transforms strings to bools.
+func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
+	vals := make([]bool, 0, len(strs))
+	parser := func(str string) (interface{}, error) {
+		val, err := parseBool(str)
+		return val, err
+	}
+	rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+	if err == nil {
+		for _, val := range rawVals {
+			vals = append(vals, val.(bool))
+		}
+	}
+	return vals, err
+}
+
+// parseFloat64s transforms strings to float64s.
+func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
+	vals := make([]float64, 0, len(strs))
+	parser := func(str string) (interface{}, error) {
+		val, err := strconv.ParseFloat(str, 64)
+		return val, err
+	}
+	rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+	if err == nil {
+		for _, val := range rawVals {
+			vals = append(vals, val.(float64))
+		}
+	}
+	return vals, err
+}
+
+// parseInts transforms strings to ints.
+func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
+	vals := make([]int, 0, len(strs))
+	parser := func(str string) (interface{}, error) {
+		val, err := strconv.ParseInt(str, 0, 64)
+		return val, err
+	}
+	rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+	if err == nil {
+		for _, val := range rawVals {
+			vals = append(vals, int(val.(int64)))
+		}
+	}
+	return vals, err
+}
+
+// parseInt64s transforms strings to int64s.
+func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
+	vals := make([]int64, 0, len(strs))
+	parser := func(str string) (interface{}, error) {
+		val, err := strconv.ParseInt(str, 0, 64)
+		return val, err
+	}
+
+	rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+	if err == nil {
+		for _, val := range rawVals {
+			vals = append(vals, val.(int64))
+		}
+	}
+	return vals, err
+}
+
+// parseUints transforms strings to uints.
+func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
+	vals := make([]uint, 0, len(strs))
+	parser := func(str string) (interface{}, error) {
+		val, err := strconv.ParseUint(str, 0, 64)
+		return val, err
+	}
+
+	rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+	if err == nil {
+		for _, val := range rawVals {
+			vals = append(vals, uint(val.(uint64)))
+		}
+	}
+	return vals, err
+}
+
+// parseUint64s transforms strings to uint64s.
+func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
+	vals := make([]uint64, 0, len(strs))
+	parser := func(str string) (interface{}, error) {
+		val, err := strconv.ParseUint(str, 0, 64)
+		return val, err
+	}
+	rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+	if err == nil {
+		for _, val := range rawVals {
+			vals = append(vals, val.(uint64))
+		}
+	}
+	return vals, err
+}
+
+type Parser func(str string) (interface{}, error)
+
+// parseTimesFormat transforms strings to times in given format.
+func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
+	vals := make([]time.Time, 0, len(strs))
+	parser := func(str string) (interface{}, error) {
+		val, err := time.Parse(format, str)
+		return val, err
+	}
+	rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+	if err == nil {
+		for _, val := range rawVals {
+			vals = append(vals, val.(time.Time))
+		}
+	}
+	return vals, err
+}
+
+// doParse transforms strings to different types
+func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) {
+	vals := make([]interface{}, 0, len(strs))
+	for _, str := range strs {
+		val, err := parser(str)
+		if err != nil && returnOnInvalid {
+			return nil, err
+		}
+		if err == nil || addInvalid {
+			vals = append(vals, val)
+		}
+	}
+	return vals, nil
+}
+
+// SetValue changes key value.
+func (k *Key) SetValue(v string) {
+	if k.s.f.BlockMode {
+		k.s.f.lock.Lock()
+		defer k.s.f.lock.Unlock()
+	}
+
+	k.value = v
+	k.s.keysHash[k.name] = v
+}
diff --git a/vendor/github.com/go-ini/ini/parser.go b/vendor/github.com/go-ini/ini/parser.go
new file mode 100644
index 000000000..44fc526c2
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/parser.go
@@ -0,0 +1,520 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"regexp"
+	"strconv"
+	"strings"
+	"unicode"
+)
+
+const minReaderBufferSize = 4096
+
+var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
+
+type parserOptions struct {
+	IgnoreContinuation          bool
+	IgnoreInlineComment         bool
+	AllowPythonMultilineValues  bool
+	SpaceBeforeInlineComment    bool
+	UnescapeValueDoubleQuotes   bool
+	UnescapeValueCommentSymbols bool
+	PreserveSurroundedQuote     bool
+	DebugFunc                   DebugFunc
+	ReaderBufferSize            int
+}
+
+type parser struct {
+	buf     *bufio.Reader
+	options parserOptions
+
+	isEOF   bool
+	count   int
+	comment *bytes.Buffer
+}
+
+func (p *parser) debug(format string, args ...interface{}) {
+	if p.options.DebugFunc != nil {
+		p.options.DebugFunc(fmt.Sprintf(format, args...))
+	}
+}
+
+func newParser(r io.Reader, opts parserOptions) *parser {
+	size := opts.ReaderBufferSize
+	if size < minReaderBufferSize {
+		size = minReaderBufferSize
+	}
+
+	return &parser{
+		buf:     bufio.NewReaderSize(r, size),
+		options: opts,
+		count:   1,
+		comment: &bytes.Buffer{},
+	}
+}
+
+// BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
+// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
+func (p *parser) BOM() error {
+	mask, err := p.buf.Peek(2)
+	if err != nil && err != io.EOF {
+		return err
+	} else if len(mask) < 2 {
+		return nil
+	}
+
+	switch {
+	case mask[0] == 254 && mask[1] == 255:
+		fallthrough
+	case mask[0] == 255 && mask[1] == 254:
+		_, err = p.buf.Read(mask)
+		if err != nil {
+			return err
+		}
+	case mask[0] == 239 && mask[1] == 187:
+		mask, err := p.buf.Peek(3)
+		if err != nil && err != io.EOF {
+			return err
+		} else if len(mask) < 3 {
+			return nil
+		}
+		if mask[2] == 191 {
+			_, err = p.buf.Read(mask)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (p *parser) readUntil(delim byte) ([]byte, error) {
+	data, err := p.buf.ReadBytes(delim)
+	if err != nil {
+		if err == io.EOF {
+			p.isEOF = true
+		} else {
+			return nil, err
+		}
+	}
+	return data, nil
+}
+
+func cleanComment(in []byte) ([]byte, bool) {
+	i := bytes.IndexAny(in, "#;")
+	if i == -1 {
+		return nil, false
+	}
+	return in[i:], true
+}
+
+func readKeyName(delimiters string, in []byte) (string, int, error) {
+	line := string(in)
+
+	// Check if key name surrounded by quotes.
+	var keyQuote string
+	if line[0] == '"' {
+		if len(line) > 6 && line[0:3] == `"""` {
+			keyQuote = `"""`
+		} else {
+			keyQuote = `"`
+		}
+	} else if line[0] == '`' {
+		keyQuote = "`"
+	}
+
+	// Get out key name
+	var endIdx int
+	if len(keyQuote) > 0 {
+		startIdx := len(keyQuote)
+		// FIXME: fail case -> """"""name"""=value
+		pos := strings.Index(line[startIdx:], keyQuote)
+		if pos == -1 {
+			return "", -1, fmt.Errorf("missing closing key quote: %s", line)
+		}
+		pos += startIdx
+
+		// Find key-value delimiter
+		i := strings.IndexAny(line[pos+startIdx:], delimiters)
+		if i < 0 {
+			return "", -1, ErrDelimiterNotFound{line}
+		}
+		endIdx = pos + i
+		return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
+	}
+
+	endIdx = strings.IndexAny(line, delimiters)
+	if endIdx < 0 {
+		return "", -1, ErrDelimiterNotFound{line}
+	}
+	if endIdx == 0 {
+		return "", -1, ErrEmptyKeyName{line}
+	}
+
+	return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
+}
+
+func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
+	for {
+		data, err := p.readUntil('\n')
+		if err != nil {
+			return "", err
+		}
+		next := string(data)
+
+		pos := strings.LastIndex(next, valQuote)
+		if pos > -1 {
+			val += next[:pos]
+
+			comment, has := cleanComment([]byte(next[pos:]))
+			if has {
+				p.comment.Write(bytes.TrimSpace(comment))
+			}
+			break
+		}
+		val += next
+		if p.isEOF {
+			return "", fmt.Errorf("missing closing key quote from %q to %q", line, next)
+		}
+	}
+	return val, nil
+}
+
+func (p *parser) readContinuationLines(val string) (string, error) {
+	for {
+		data, err := p.readUntil('\n')
+		if err != nil {
+			return "", err
+		}
+		next := strings.TrimSpace(string(data))
+
+		if len(next) == 0 {
+			break
+		}
+		val += next
+		if val[len(val)-1] != '\\' {
+			break
+		}
+		val = val[:len(val)-1]
+	}
+	return val, nil
+}
+
+// hasSurroundedQuote check if and only if the first and last characters
+// are quotes \" or \'.
+// It returns false if any other parts also contain same kind of quotes.
+func hasSurroundedQuote(in string, quote byte) bool {
+	return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
+		strings.IndexByte(in[1:], quote) == len(in)-2
+}
+
+func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
+
+	line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
+	if len(line) == 0 {
+		if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' {
+			return p.readPythonMultilines(line, bufferSize)
+		}
+		return "", nil
+	}
+
+	var valQuote string
+	if len(line) > 3 && line[0:3] == `"""` {
+		valQuote = `"""`
+	} else if line[0] == '`' {
+		valQuote = "`"
+	} else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' {
+		valQuote = `"`
+	}
+
+	if len(valQuote) > 0 {
+		startIdx := len(valQuote)
+		pos := strings.LastIndex(line[startIdx:], valQuote)
+		// Check for multi-line value
+		if pos == -1 {
+			return p.readMultilines(line, line[startIdx:], valQuote)
+		}
+
+		if p.options.UnescapeValueDoubleQuotes && valQuote == `"` {
+			return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
+		}
+		return line[startIdx : pos+startIdx], nil
+	}
+
+	lastChar := line[len(line)-1]
+	// Won't be able to reach here if value only contains whitespace
+	line = strings.TrimSpace(line)
+	trimmedLastChar := line[len(line)-1]
+
+	// Check continuation lines when desired
+	if !p.options.IgnoreContinuation && trimmedLastChar == '\\' {
+		return p.readContinuationLines(line[:len(line)-1])
+	}
+
+	// Check if ignore inline comment
+	if !p.options.IgnoreInlineComment {
+		var i int
+		if p.options.SpaceBeforeInlineComment {
+			i = strings.Index(line, " #")
+			if i == -1 {
+				i = strings.Index(line, " ;")
+			}
+
+		} else {
+			i = strings.IndexAny(line, "#;")
+		}
+
+		if i > -1 {
+			p.comment.WriteString(line[i:])
+			line = strings.TrimSpace(line[:i])
+		}
+
+	}
+
+	// Trim single and double quotes
+	if (hasSurroundedQuote(line, '\'') ||
+		hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
+		line = line[1 : len(line)-1]
+	} else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
+		line = strings.ReplaceAll(line, `\;`, ";")
+		line = strings.ReplaceAll(line, `\#`, "#")
+	} else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
+		return p.readPythonMultilines(line, bufferSize)
+	}
+
+	return line, nil
+}
+
+func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) {
+	parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
+	peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+	for {
+		peekData, peekErr := peekBuffer.ReadBytes('\n')
+		if peekErr != nil && peekErr != io.EOF {
+			p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
+			return "", peekErr
+		}
+
+		p.debug("readPythonMultilines: parsing %q", string(peekData))
+
+		peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+		p.debug("readPythonMultilines: matched %d parts", len(peekMatches))
+		for n, v := range peekMatches {
+			p.debug("   %d: %q", n, v)
+		}
+
+		// Return if not a Python multiline value.
+		if len(peekMatches) != 3 {
+			p.debug("readPythonMultilines: end of value, got: %q", line)
+			return line, nil
+		}
+
+		// Advance the parser reader (buffer) in-sync with the peek buffer.
+		_, err := p.buf.Discard(len(peekData))
+		if err != nil {
+			p.debug("readPythonMultilines: failed to skip to the end, returning error")
+			return "", err
+		}
+
+		line += "\n" + peekMatches[0]
+	}
+}
+
+// parse parses data through an io.Reader.
+func (f *File) parse(reader io.Reader) (err error) {
+	p := newParser(reader, parserOptions{
+		IgnoreContinuation:          f.options.IgnoreContinuation,
+		IgnoreInlineComment:         f.options.IgnoreInlineComment,
+		AllowPythonMultilineValues:  f.options.AllowPythonMultilineValues,
+		SpaceBeforeInlineComment:    f.options.SpaceBeforeInlineComment,
+		UnescapeValueDoubleQuotes:   f.options.UnescapeValueDoubleQuotes,
+		UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
+		PreserveSurroundedQuote:     f.options.PreserveSurroundedQuote,
+		DebugFunc:                   f.options.DebugFunc,
+		ReaderBufferSize:            f.options.ReaderBufferSize,
+	})
+	if err = p.BOM(); err != nil {
+		return fmt.Errorf("BOM: %v", err)
+	}
+
+	// Ignore error because default section name is never empty string.
+	name := DefaultSection
+	if f.options.Insensitive || f.options.InsensitiveSections {
+		name = strings.ToLower(DefaultSection)
+	}
+	section, _ := f.NewSection(name)
+
+	// This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
+	var isLastValueEmpty bool
+	var lastRegularKey *Key
+
+	var line []byte
+	var inUnparseableSection bool
+
+	// NOTE: Iterate and increase `currentPeekSize` until
+	// the size of the parser buffer is found.
+	// TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
+	parserBufferSize := 0
+	// NOTE: Peek 4kb at a time.
+	currentPeekSize := minReaderBufferSize
+
+	if f.options.AllowPythonMultilineValues {
+		for {
+			peekBytes, _ := p.buf.Peek(currentPeekSize)
+			peekBytesLength := len(peekBytes)
+
+			if parserBufferSize >= peekBytesLength {
+				break
+			}
+
+			currentPeekSize *= 2
+			parserBufferSize = peekBytesLength
+		}
+	}
+
+	for !p.isEOF {
+		line, err = p.readUntil('\n')
+		if err != nil {
+			return err
+		}
+
+		if f.options.AllowNestedValues &&
+			isLastValueEmpty && len(line) > 0 {
+			if line[0] == ' ' || line[0] == '\t' {
+				err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
+				if err != nil {
+					return err
+				}
+				continue
+			}
+		}
+
+		line = bytes.TrimLeftFunc(line, unicode.IsSpace)
+		if len(line) == 0 {
+			continue
+		}
+
+		// Comments
+		if line[0] == '#' || line[0] == ';' {
+			// Note: we do not care ending line break,
+			// it is needed for adding second line,
+			// so just clean it once at the end when set to value.
+			p.comment.Write(line)
+			continue
+		}
+
+		// Section
+		if line[0] == '[' {
+			// Read to the next ']' (TODO: support quoted strings)
+			closeIdx := bytes.LastIndexByte(line, ']')
+			if closeIdx == -1 {
+				return fmt.Errorf("unclosed section: %s", line)
+			}
+
+			name := string(line[1:closeIdx])
+			section, err = f.NewSection(name)
+			if err != nil {
+				return err
+			}
+
+			comment, has := cleanComment(line[closeIdx+1:])
+			if has {
+				p.comment.Write(comment)
+			}
+
+			section.Comment = strings.TrimSpace(p.comment.String())
+
+			// Reset auto-counter and comments
+			p.comment.Reset()
+			p.count = 1
+			// Nested values can't span sections
+			isLastValueEmpty = false
+
+			inUnparseableSection = false
+			for i := range f.options.UnparseableSections {
+				if f.options.UnparseableSections[i] == name ||
+					((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
+					inUnparseableSection = true
+					continue
+				}
+			}
+			continue
+		}
+
+		if inUnparseableSection {
+			section.isRawSection = true
+			section.rawBody += string(line)
+			continue
+		}
+
+		kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
+		if err != nil {
+			switch {
+			// Treat as boolean key when desired, and whole line is key name.
+			case IsErrDelimiterNotFound(err):
+				switch {
+				case f.options.AllowBooleanKeys:
+					kname, err := p.readValue(line, parserBufferSize)
+					if err != nil {
+						return err
+					}
+					key, err := section.NewBooleanKey(kname)
+					if err != nil {
+						return err
+					}
+					key.Comment = strings.TrimSpace(p.comment.String())
+					p.comment.Reset()
+					continue
+
+				case f.options.SkipUnrecognizableLines:
+					continue
+				}
+			case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines:
+				continue
+			}
+			return err
+		}
+
+		// Auto increment.
+		isAutoIncr := false
+		if kname == "-" {
+			isAutoIncr = true
+			kname = "#" + strconv.Itoa(p.count)
+			p.count++
+		}
+
+		value, err := p.readValue(line[offset:], parserBufferSize)
+		if err != nil {
+			return err
+		}
+		isLastValueEmpty = len(value) == 0
+
+		key, err := section.NewKey(kname, value)
+		if err != nil {
+			return err
+		}
+		key.isAutoIncrement = isAutoIncr
+		key.Comment = strings.TrimSpace(p.comment.String())
+		p.comment.Reset()
+		lastRegularKey = key
+	}
+	return nil
+}
diff --git a/vendor/github.com/go-ini/ini/section.go b/vendor/github.com/go-ini/ini/section.go
new file mode 100644
index 000000000..a3615d820
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/section.go
@@ -0,0 +1,256 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+)
+
+// Section represents a config section.
+type Section struct {
+	f        *File
+	Comment  string
+	name     string
+	keys     map[string]*Key
+	keyList  []string
+	keysHash map[string]string
+
+	isRawSection bool
+	rawBody      string
+}
+
+func newSection(f *File, name string) *Section {
+	return &Section{
+		f:        f,
+		name:     name,
+		keys:     make(map[string]*Key),
+		keyList:  make([]string, 0, 10),
+		keysHash: make(map[string]string),
+	}
+}
+
+// Name returns name of Section.
+func (s *Section) Name() string {
+	return s.name
+}
+
+// Body returns rawBody of Section if the section was marked as unparseable.
+// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
+func (s *Section) Body() string {
+	return strings.TrimSpace(s.rawBody)
+}
+
+// SetBody updates body content only if section is raw.
+func (s *Section) SetBody(body string) {
+	if !s.isRawSection {
+		return
+	}
+	s.rawBody = body
+}
+
+// NewKey creates a new key to given section.
+func (s *Section) NewKey(name, val string) (*Key, error) {
+	if len(name) == 0 {
+		return nil, errors.New("error creating new key: empty key name")
+	} else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+		name = strings.ToLower(name)
+	}
+
+	if s.f.BlockMode {
+		s.f.lock.Lock()
+		defer s.f.lock.Unlock()
+	}
+
+	if inSlice(name, s.keyList) {
+		if s.f.options.AllowShadows {
+			if err := s.keys[name].addShadow(val); err != nil {
+				return nil, err
+			}
+		} else {
+			s.keys[name].value = val
+			s.keysHash[name] = val
+		}
+		return s.keys[name], nil
+	}
+
+	s.keyList = append(s.keyList, name)
+	s.keys[name] = newKey(s, name, val)
+	s.keysHash[name] = val
+	return s.keys[name], nil
+}
+
+// NewBooleanKey creates a new boolean type key to given section.
+func (s *Section) NewBooleanKey(name string) (*Key, error) {
+	key, err := s.NewKey(name, "true")
+	if err != nil {
+		return nil, err
+	}
+
+	key.isBooleanType = true
+	return key, nil
+}
+
+// GetKey returns key in section by given name.
+func (s *Section) GetKey(name string) (*Key, error) {
+	if s.f.BlockMode {
+		s.f.lock.RLock()
+	}
+	if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+		name = strings.ToLower(name)
+	}
+	key := s.keys[name]
+	if s.f.BlockMode {
+		s.f.lock.RUnlock()
+	}
+
+	if key == nil {
+		// Check if it is a child-section.
+		sname := s.name
+		for {
+			if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+				sname = sname[:i]
+				sec, err := s.f.GetSection(sname)
+				if err != nil {
+					continue
+				}
+				return sec.GetKey(name)
+			}
+			break
+		}
+		return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name)
+	}
+	return key, nil
+}
+
+// HasKey returns true if section contains a key with given name.
+func (s *Section) HasKey(name string) bool {
+	key, _ := s.GetKey(name)
+	return key != nil
+}
+
+// Deprecated: Use "HasKey" instead.
+func (s *Section) Haskey(name string) bool {
+	return s.HasKey(name)
+}
+
+// HasValue returns true if section contains given raw value.
+func (s *Section) HasValue(value string) bool {
+	if s.f.BlockMode {
+		s.f.lock.RLock()
+		defer s.f.lock.RUnlock()
+	}
+
+	for _, k := range s.keys {
+		if value == k.value {
+			return true
+		}
+	}
+	return false
+}
+
+// Key assumes named Key exists in section and returns a zero-value when not.
+func (s *Section) Key(name string) *Key {
+	key, err := s.GetKey(name)
+	if err != nil {
+		// It's OK here because the only possible error is empty key name,
+		// but if it's empty, this piece of code won't be executed.
+		key, _ = s.NewKey(name, "")
+		return key
+	}
+	return key
+}
+
+// Keys returns list of keys of section.
+func (s *Section) Keys() []*Key {
+	keys := make([]*Key, len(s.keyList))
+	for i := range s.keyList {
+		keys[i] = s.Key(s.keyList[i])
+	}
+	return keys
+}
+
+// ParentKeys returns list of keys of parent section.
+func (s *Section) ParentKeys() []*Key {
+	var parentKeys []*Key
+	sname := s.name
+	for {
+		if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+			sname = sname[:i]
+			sec, err := s.f.GetSection(sname)
+			if err != nil {
+				continue
+			}
+			parentKeys = append(parentKeys, sec.Keys()...)
+		} else {
+			break
+		}
+
+	}
+	return parentKeys
+}
+
+// KeyStrings returns list of key names of section.
+func (s *Section) KeyStrings() []string {
+	list := make([]string, len(s.keyList))
+	copy(list, s.keyList)
+	return list
+}
+
+// KeysHash returns keys hash consisting of names and values.
+func (s *Section) KeysHash() map[string]string {
+	if s.f.BlockMode {
+		s.f.lock.RLock()
+		defer s.f.lock.RUnlock()
+	}
+
+	hash := make(map[string]string, len(s.keysHash))
+	for key, value := range s.keysHash {
+		hash[key] = value
+	}
+	return hash
+}
+
+// DeleteKey deletes a key from section.
+func (s *Section) DeleteKey(name string) {
+	if s.f.BlockMode {
+		s.f.lock.Lock()
+		defer s.f.lock.Unlock()
+	}
+
+	for i, k := range s.keyList {
+		if k == name {
+			s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
+			delete(s.keys, name)
+			delete(s.keysHash, name)
+			return
+		}
+	}
+}
+
+// ChildSections returns a list of child sections of current section.
+// For example, "[parent.child1]" and "[parent.child12]" are child sections
+// of section "[parent]".
+func (s *Section) ChildSections() []*Section {
+	prefix := s.name + s.f.options.ChildSectionDelimiter
+	children := make([]*Section, 0, 3)
+	for _, name := range s.f.sectionList {
+		if strings.HasPrefix(name, prefix) {
+			children = append(children, s.f.sections[name]...)
+		}
+	}
+	return children
+}
diff --git a/vendor/github.com/go-ini/ini/struct.go b/vendor/github.com/go-ini/ini/struct.go
new file mode 100644
index 000000000..a486b2fe0
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/struct.go
@@ -0,0 +1,747 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+	"time"
+	"unicode"
+)
+
+// NameMapper represents a ini tag name mapper.
+type NameMapper func(string) string
+
+// Built-in name getters.
+var (
+	// SnackCase converts to format SNACK_CASE.
+	SnackCase NameMapper = func(raw string) string {
+		newstr := make([]rune, 0, len(raw))
+		for i, chr := range raw {
+			if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+				if i > 0 {
+					newstr = append(newstr, '_')
+				}
+			}
+			newstr = append(newstr, unicode.ToUpper(chr))
+		}
+		return string(newstr)
+	}
+	// TitleUnderscore converts to format title_underscore.
+	TitleUnderscore NameMapper = func(raw string) string {
+		newstr := make([]rune, 0, len(raw))
+		for i, chr := range raw {
+			if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+				if i > 0 {
+					newstr = append(newstr, '_')
+				}
+				chr -= 'A' - 'a'
+			}
+			newstr = append(newstr, chr)
+		}
+		return string(newstr)
+	}
+)
+
+func (s *Section) parseFieldName(raw, actual string) string {
+	if len(actual) > 0 {
+		return actual
+	}
+	if s.f.NameMapper != nil {
+		return s.f.NameMapper(raw)
+	}
+	return raw
+}
+
+func parseDelim(actual string) string {
+	if len(actual) > 0 {
+		return actual
+	}
+	return ","
+}
+
+var reflectTime = reflect.TypeOf(time.Now()).Kind()
+
+// setSliceWithProperType sets proper values to slice based on its type.
+func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+	var strs []string
+	if allowShadow {
+		strs = key.StringsWithShadows(delim)
+	} else {
+		strs = key.Strings(delim)
+	}
+
+	numVals := len(strs)
+	if numVals == 0 {
+		return nil
+	}
+
+	var vals interface{}
+	var err error
+
+	sliceOf := field.Type().Elem().Kind()
+	switch sliceOf {
+	case reflect.String:
+		vals = strs
+	case reflect.Int:
+		vals, err = key.parseInts(strs, true, false)
+	case reflect.Int64:
+		vals, err = key.parseInt64s(strs, true, false)
+	case reflect.Uint:
+		vals, err = key.parseUints(strs, true, false)
+	case reflect.Uint64:
+		vals, err = key.parseUint64s(strs, true, false)
+	case reflect.Float64:
+		vals, err = key.parseFloat64s(strs, true, false)
+	case reflect.Bool:
+		vals, err = key.parseBools(strs, true, false)
+	case reflectTime:
+		vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
+	default:
+		return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+	}
+	if err != nil && isStrict {
+		return err
+	}
+
+	slice := reflect.MakeSlice(field.Type(), numVals, numVals)
+	for i := 0; i < numVals; i++ {
+		switch sliceOf {
+		case reflect.String:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
+		case reflect.Int:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
+		case reflect.Int64:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
+		case reflect.Uint:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
+		case reflect.Uint64:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
+		case reflect.Float64:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
+		case reflect.Bool:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i]))
+		case reflectTime:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
+		}
+	}
+	field.Set(slice)
+	return nil
+}
+
+func wrapStrictError(err error, isStrict bool) error {
+	if isStrict {
+		return err
+	}
+	return nil
+}
+
+// setWithProperType sets proper value to field based on its type,
+// but it does not return error for failing parsing,
+// because we want to use default value that is already assigned to struct.
+func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+	vt := t
+	isPtr := t.Kind() == reflect.Ptr
+	if isPtr {
+		vt = t.Elem()
+	}
+	switch vt.Kind() {
+	case reflect.String:
+		stringVal := key.String()
+		if isPtr {
+			field.Set(reflect.ValueOf(&stringVal))
+		} else if len(stringVal) > 0 {
+			field.SetString(key.String())
+		}
+	case reflect.Bool:
+		boolVal, err := key.Bool()
+		if err != nil {
+			return wrapStrictError(err, isStrict)
+		}
+		if isPtr {
+			field.Set(reflect.ValueOf(&boolVal))
+		} else {
+			field.SetBool(boolVal)
+		}
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		// ParseDuration will not return err for `0`, so check the type name
+		if vt.Name() == "Duration" {
+			durationVal, err := key.Duration()
+			if err != nil {
+				if intVal, err := key.Int64(); err == nil {
+					field.SetInt(intVal)
+					return nil
+				}
+				return wrapStrictError(err, isStrict)
+			}
+			if isPtr {
+				field.Set(reflect.ValueOf(&durationVal))
+			} else if int64(durationVal) > 0 {
+				field.Set(reflect.ValueOf(durationVal))
+			}
+			return nil
+		}
+
+		intVal, err := key.Int64()
+		if err != nil {
+			return wrapStrictError(err, isStrict)
+		}
+		if isPtr {
+			pv := reflect.New(t.Elem())
+			pv.Elem().SetInt(intVal)
+			field.Set(pv)
+		} else {
+			field.SetInt(intVal)
+		}
+	//	byte is an alias for uint8, so supporting uint8 breaks support for byte
+	case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		durationVal, err := key.Duration()
+		// Skip zero value
+		if err == nil && uint64(durationVal) > 0 {
+			if isPtr {
+				field.Set(reflect.ValueOf(&durationVal))
+			} else {
+				field.Set(reflect.ValueOf(durationVal))
+			}
+			return nil
+		}
+
+		uintVal, err := key.Uint64()
+		if err != nil {
+			return wrapStrictError(err, isStrict)
+		}
+		if isPtr {
+			pv := reflect.New(t.Elem())
+			pv.Elem().SetUint(uintVal)
+			field.Set(pv)
+		} else {
+			field.SetUint(uintVal)
+		}
+
+	case reflect.Float32, reflect.Float64:
+		floatVal, err := key.Float64()
+		if err != nil {
+			return wrapStrictError(err, isStrict)
+		}
+		if isPtr {
+			pv := reflect.New(t.Elem())
+			pv.Elem().SetFloat(floatVal)
+			field.Set(pv)
+		} else {
+			field.SetFloat(floatVal)
+		}
+	case reflectTime:
+		timeVal, err := key.Time()
+		if err != nil {
+			return wrapStrictError(err, isStrict)
+		}
+		if isPtr {
+			field.Set(reflect.ValueOf(&timeVal))
+		} else {
+			field.Set(reflect.ValueOf(timeVal))
+		}
+	case reflect.Slice:
+		return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
+	default:
+		return fmt.Errorf("unsupported type %q", t)
+	}
+	return nil
+}
+
+func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
+	opts := strings.SplitN(tag, ",", 5)
+	rawName = opts[0]
+	for _, opt := range opts[1:] {
+		omitEmpty = omitEmpty || (opt == "omitempty")
+		allowShadow = allowShadow || (opt == "allowshadow")
+		allowNonUnique = allowNonUnique || (opt == "nonunique")
+		extends = extends || (opt == "extends")
+	}
+	return rawName, omitEmpty, allowShadow, allowNonUnique, extends
+}
+
+// mapToField maps the given value to the matching field of the given section.
+// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
+func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
+	if val.Kind() == reflect.Ptr {
+		val = val.Elem()
+	}
+	typ := val.Type()
+
+	for i := 0; i < typ.NumField(); i++ {
+		field := val.Field(i)
+		tpField := typ.Field(i)
+
+		tag := tpField.Tag.Get("ini")
+		if tag == "-" {
+			continue
+		}
+
+		rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+		fieldName := s.parseFieldName(tpField.Name, rawName)
+		if len(fieldName) == 0 || !field.CanSet() {
+			continue
+		}
+
+		isStruct := tpField.Type.Kind() == reflect.Struct
+		isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
+		isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
+		if isAnonymousPtr {
+			field.Set(reflect.New(tpField.Type.Elem()))
+		}
+
+		if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
+			if isStructPtr && field.IsNil() {
+				field.Set(reflect.New(tpField.Type.Elem()))
+			}
+			fieldSection := s
+			if rawName != "" {
+				sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
+				if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
+					fieldSection = secs[sectionIndex]
+				}
+			}
+			if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
+				return fmt.Errorf("map to field %q: %v", fieldName, err)
+			}
+		} else if isAnonymousPtr || isStruct || isStructPtr {
+			if secs, err := s.f.SectionsByName(fieldName); err == nil {
+				if len(secs) <= sectionIndex {
+					return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
+				}
+				// Only set the field to non-nil struct value if we have a section for it.
+				// Otherwise, we end up with a non-nil struct ptr even though there is no data.
+				if isStructPtr && field.IsNil() {
+					field.Set(reflect.New(tpField.Type.Elem()))
+				}
+				if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
+					return fmt.Errorf("map to field %q: %v", fieldName, err)
+				}
+				continue
+			}
+		}
+
+		// Map non-unique sections
+		if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+			newField, err := s.mapToSlice(fieldName, field, isStrict)
+			if err != nil {
+				return fmt.Errorf("map to slice %q: %v", fieldName, err)
+			}
+
+			field.Set(newField)
+			continue
+		}
+
+		if key, err := s.GetKey(fieldName); err == nil {
+			delim := parseDelim(tpField.Tag.Get("delim"))
+			if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
+				return fmt.Errorf("set field %q: %v", fieldName, err)
+			}
+		}
+	}
+	return nil
+}
+
+// mapToSlice maps all sections with the same name and returns the new value.
+// The type of the Value must be a slice.
+func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) {
+	secs, err := s.f.SectionsByName(secName)
+	if err != nil {
+		return reflect.Value{}, err
+	}
+
+	typ := val.Type().Elem()
+	for i, sec := range secs {
+		elem := reflect.New(typ)
+		if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
+			return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
+		}
+
+		val = reflect.Append(val, elem.Elem())
+	}
+	return val, nil
+}
+
+// mapTo maps a section to object v.
+func (s *Section) mapTo(v interface{}, isStrict bool) error {
+	typ := reflect.TypeOf(v)
+	val := reflect.ValueOf(v)
+	if typ.Kind() == reflect.Ptr {
+		typ = typ.Elem()
+		val = val.Elem()
+	} else {
+		return errors.New("not a pointer to a struct")
+	}
+
+	if typ.Kind() == reflect.Slice {
+		newField, err := s.mapToSlice(s.name, val, isStrict)
+		if err != nil {
+			return err
+		}
+
+		val.Set(newField)
+		return nil
+	}
+
+	return s.mapToField(val, isStrict, 0, s.name)
+}
+
+// MapTo maps section to given struct.
+func (s *Section) MapTo(v interface{}) error {
+	return s.mapTo(v, false)
+}
+
+// StrictMapTo maps section to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (s *Section) StrictMapTo(v interface{}) error {
+	return s.mapTo(v, true)
+}
+
+// MapTo maps file to given struct.
+func (f *File) MapTo(v interface{}) error {
+	return f.Section("").MapTo(v)
+}
+
+// StrictMapTo maps file to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (f *File) StrictMapTo(v interface{}) error {
+	return f.Section("").StrictMapTo(v)
+}
+
+// MapToWithMapper maps data sources to given struct with name mapper.
+func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+	cfg, err := Load(source, others...)
+	if err != nil {
+		return err
+	}
+	cfg.NameMapper = mapper
+	return cfg.MapTo(v)
+}
+
+// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
+// which returns all possible error including value parsing error.
+func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+	cfg, err := Load(source, others...)
+	if err != nil {
+		return err
+	}
+	cfg.NameMapper = mapper
+	return cfg.StrictMapTo(v)
+}
+
+// MapTo maps data sources to given struct.
+func MapTo(v, source interface{}, others ...interface{}) error {
+	return MapToWithMapper(v, nil, source, others...)
+}
+
+// StrictMapTo maps data sources to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func StrictMapTo(v, source interface{}, others ...interface{}) error {
+	return StrictMapToWithMapper(v, nil, source, others...)
+}
+
+// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
+func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
+	slice := field.Slice(0, field.Len())
+	if field.Len() == 0 {
+		return nil
+	}
+	sliceOf := field.Type().Elem().Kind()
+
+	if allowShadow {
+		var keyWithShadows *Key
+		for i := 0; i < field.Len(); i++ {
+			var val string
+			switch sliceOf {
+			case reflect.String:
+				val = slice.Index(i).String()
+			case reflect.Int, reflect.Int64:
+				val = fmt.Sprint(slice.Index(i).Int())
+			case reflect.Uint, reflect.Uint64:
+				val = fmt.Sprint(slice.Index(i).Uint())
+			case reflect.Float64:
+				val = fmt.Sprint(slice.Index(i).Float())
+			case reflect.Bool:
+				val = fmt.Sprint(slice.Index(i).Bool())
+			case reflectTime:
+				val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339)
+			default:
+				return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+			}
+
+			if i == 0 {
+				keyWithShadows = newKey(key.s, key.name, val)
+			} else {
+				_ = keyWithShadows.AddShadow(val)
+			}
+		}
+		*key = *keyWithShadows
+		return nil
+	}
+
+	var buf bytes.Buffer
+	for i := 0; i < field.Len(); i++ {
+		switch sliceOf {
+		case reflect.String:
+			buf.WriteString(slice.Index(i).String())
+		case reflect.Int, reflect.Int64:
+			buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
+		case reflect.Uint, reflect.Uint64:
+			buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
+		case reflect.Float64:
+			buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
+		case reflect.Bool:
+			buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
+		case reflectTime:
+			buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
+		default:
+			return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+		}
+		buf.WriteString(delim)
+	}
+	key.SetValue(buf.String()[:buf.Len()-len(delim)])
+	return nil
+}
+
+// reflectWithProperType does the opposite thing as setWithProperType.
+func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
+	switch t.Kind() {
+	case reflect.String:
+		key.SetValue(field.String())
+	case reflect.Bool:
+		key.SetValue(fmt.Sprint(field.Bool()))
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		key.SetValue(fmt.Sprint(field.Int()))
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		key.SetValue(fmt.Sprint(field.Uint()))
+	case reflect.Float32, reflect.Float64:
+		key.SetValue(fmt.Sprint(field.Float()))
+	case reflectTime:
+		key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
+	case reflect.Slice:
+		return reflectSliceWithProperType(key, field, delim, allowShadow)
+	case reflect.Ptr:
+		if !field.IsNil() {
+			return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
+		}
+	default:
+		return fmt.Errorf("unsupported type %q", t)
+	}
+	return nil
+}
+
+// CR: copied from encoding/json/encode.go with modifications of time.Time support.
+// TODO: add more test coverage.
+func isEmptyValue(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() == 0
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return v.IsNil()
+	case reflectTime:
+		t, ok := v.Interface().(time.Time)
+		return ok && t.IsZero()
+	}
+	return false
+}
+
+// StructReflector is the interface implemented by struct types that can extract themselves into INI objects.
+type StructReflector interface {
+	ReflectINIStruct(*File) error
+}
+
+func (s *Section) reflectFrom(val reflect.Value) error {
+	if val.Kind() == reflect.Ptr {
+		val = val.Elem()
+	}
+	typ := val.Type()
+
+	for i := 0; i < typ.NumField(); i++ {
+		if !val.Field(i).CanInterface() {
+			continue
+		}
+
+		field := val.Field(i)
+		tpField := typ.Field(i)
+
+		tag := tpField.Tag.Get("ini")
+		if tag == "-" {
+			continue
+		}
+
+		rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+		if omitEmpty && isEmptyValue(field) {
+			continue
+		}
+
+		if r, ok := field.Interface().(StructReflector); ok {
+			return r.ReflectINIStruct(s.f)
+		}
+
+		fieldName := s.parseFieldName(tpField.Name, rawName)
+		if len(fieldName) == 0 || !field.CanSet() {
+			continue
+		}
+
+		if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
+			if err := s.reflectFrom(field); err != nil {
+				return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+			}
+			continue
+		}
+
+		if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
+			(tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
+			// Note: The only error here is section doesn't exist.
+			sec, err := s.f.GetSection(fieldName)
+			if err != nil {
+				// Note: fieldName can never be empty here, ignore error.
+				sec, _ = s.f.NewSection(fieldName)
+			}
+
+			// Add comment from comment tag
+			if len(sec.Comment) == 0 {
+				sec.Comment = tpField.Tag.Get("comment")
+			}
+
+			if err = sec.reflectFrom(field); err != nil {
+				return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+			}
+			continue
+		}
+
+		if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+			slice := field.Slice(0, field.Len())
+			if field.Len() == 0 {
+				return nil
+			}
+			sliceOf := field.Type().Elem().Kind()
+
+			for i := 0; i < field.Len(); i++ {
+				if sliceOf != reflect.Struct && sliceOf != reflect.Ptr {
+					return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
+				}
+
+				sec, err := s.f.NewSection(fieldName)
+				if err != nil {
+					return err
+				}
+
+				// Add comment from comment tag
+				if len(sec.Comment) == 0 {
+					sec.Comment = tpField.Tag.Get("comment")
+				}
+
+				if err := sec.reflectFrom(slice.Index(i)); err != nil {
+					return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+				}
+			}
+			continue
+		}
+
+		// Note: Same reason as section.
+		key, err := s.GetKey(fieldName)
+		if err != nil {
+			key, _ = s.NewKey(fieldName, "")
+		}
+
+		// Add comment from comment tag
+		if len(key.Comment) == 0 {
+			key.Comment = tpField.Tag.Get("comment")
+		}
+
+		delim := parseDelim(tpField.Tag.Get("delim"))
+		if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
+			return fmt.Errorf("reflect field %q: %v", fieldName, err)
+		}
+
+	}
+	return nil
+}
+
+// ReflectFrom reflects section from given struct. It overwrites existing ones.
+func (s *Section) ReflectFrom(v interface{}) error {
+	typ := reflect.TypeOf(v)
+	val := reflect.ValueOf(v)
+
+	if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
+		(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) {
+		// Clear sections to make sure none exists before adding the new ones
+		s.f.DeleteSection(s.name)
+
+		if typ.Kind() == reflect.Ptr {
+			sec, err := s.f.NewSection(s.name)
+			if err != nil {
+				return err
+			}
+			return sec.reflectFrom(val.Elem())
+		}
+
+		slice := val.Slice(0, val.Len())
+		sliceOf := val.Type().Elem().Kind()
+		if sliceOf != reflect.Ptr {
+			return fmt.Errorf("not a slice of pointers")
+		}
+
+		for i := 0; i < slice.Len(); i++ {
+			sec, err := s.f.NewSection(s.name)
+			if err != nil {
+				return err
+			}
+
+			err = sec.reflectFrom(slice.Index(i))
+			if err != nil {
+				return fmt.Errorf("reflect from %dth field: %v", i, err)
+			}
+		}
+
+		return nil
+	}
+
+	if typ.Kind() == reflect.Ptr {
+		val = val.Elem()
+	} else {
+		return errors.New("not a pointer to a struct")
+	}
+
+	return s.reflectFrom(val)
+}
+
+// ReflectFrom reflects file from given struct.
+func (f *File) ReflectFrom(v interface{}) error {
+	return f.Section("").ReflectFrom(v)
+}
+
+// ReflectFromWithMapper reflects data sources from given struct with name mapper.
+func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
+	cfg.NameMapper = mapper
+	return cfg.ReflectFrom(v)
+}
+
+// ReflectFrom reflects data sources from given struct.
+func ReflectFrom(cfg *File, v interface{}) error {
+	return ReflectFromWithMapper(cfg, v, nil)
+}
diff --git a/vendor/github.com/goccy/go-json/.golangci.yml b/vendor/github.com/goccy/go-json/.golangci.yml
index 57ae5a528..977accaa9 100644
--- a/vendor/github.com/goccy/go-json/.golangci.yml
+++ b/vendor/github.com/goccy/go-json/.golangci.yml
@@ -56,6 +56,9 @@ linters:
     - cyclop
     - containedctx
     - revive
+    - nosnakecase
+    - exhaustruct
+    - depguard
 
 issues:
   exclude-rules:
diff --git a/vendor/github.com/goccy/go-json/Makefile b/vendor/github.com/goccy/go-json/Makefile
index 5bbfc4c9a..c030577dc 100644
--- a/vendor/github.com/goccy/go-json/Makefile
+++ b/vendor/github.com/goccy/go-json/Makefile
@@ -30,7 +30,7 @@ golangci-lint: | $(BIN_DIR)
 		GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \
 		cd $$GOLANGCI_LINT_TMP_DIR; \
 		go mod init tmp; \
-		GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.48.0; \
+		GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2; \
 		rm -rf $$GOLANGCI_LINT_TMP_DIR; \
 	}
 
diff --git a/vendor/github.com/goccy/go-json/encode.go b/vendor/github.com/goccy/go-json/encode.go
index 4bd899f38..c5173825a 100644
--- a/vendor/github.com/goccy/go-json/encode.go
+++ b/vendor/github.com/goccy/go-json/encode.go
@@ -52,7 +52,7 @@ func (e *Encoder) EncodeContext(ctx context.Context, v interface{}, optFuncs ...
 	rctx.Option.Flag |= encoder.ContextOption
 	rctx.Option.Context = ctx
 
-	err := e.encodeWithOption(rctx, v, optFuncs...)
+	err := e.encodeWithOption(rctx, v, optFuncs...) //nolint: contextcheck
 
 	encoder.ReleaseRuntimeContext(rctx)
 	return err
@@ -120,7 +120,7 @@ func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOption
 		optFunc(rctx.Option)
 	}
 
-	buf, err := encode(rctx, v)
+	buf, err := encode(rctx, v) //nolint: contextcheck
 	if err != nil {
 		encoder.ReleaseRuntimeContext(rctx)
 		return nil, err
diff --git a/vendor/github.com/goccy/go-json/internal/decoder/ptr.go b/vendor/github.com/goccy/go-json/internal/decoder/ptr.go
index de12e105c..ae2299466 100644
--- a/vendor/github.com/goccy/go-json/internal/decoder/ptr.go
+++ b/vendor/github.com/goccy/go-json/internal/decoder/ptr.go
@@ -85,6 +85,7 @@ func (d *ptrDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
 	}
 	c, err := d.dec.Decode(ctx, cursor, depth, newptr)
 	if err != nil {
+		*(*unsafe.Pointer)(p) = nil
 		return 0, err
 	}
 	cursor = c
diff --git a/vendor/github.com/goccy/go-json/internal/decoder/unmarshal_text.go b/vendor/github.com/goccy/go-json/internal/decoder/unmarshal_text.go
index 6d37993f0..d711d0f85 100644
--- a/vendor/github.com/goccy/go-json/internal/decoder/unmarshal_text.go
+++ b/vendor/github.com/goccy/go-json/internal/decoder/unmarshal_text.go
@@ -147,7 +147,7 @@ func (d *unmarshalTextDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int
 	return nil, 0, fmt.Errorf("json: unmarshal text decoder does not support decode path")
 }
 
-func unquoteBytes(s []byte) (t []byte, ok bool) {
+func unquoteBytes(s []byte) (t []byte, ok bool) { //nolint: nonamedreturns
 	length := len(s)
 	if length < 2 || s[0] != '"' || s[length-1] != '"' {
 		return
diff --git a/vendor/github.com/goccy/go-json/internal/encoder/compact.go b/vendor/github.com/goccy/go-json/internal/encoder/compact.go
index 0eb9545d8..e287a6c03 100644
--- a/vendor/github.com/goccy/go-json/internal/encoder/compact.go
+++ b/vendor/github.com/goccy/go-json/internal/encoder/compact.go
@@ -213,8 +213,8 @@ func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, e
 				dst = append(dst, src[start:cursor]...)
 				dst = append(dst, `\u202`...)
 				dst = append(dst, hex[src[cursor+2]&0xF])
-				cursor += 2
 				start = cursor + 3
+				cursor += 2
 			}
 		}
 		switch c {
diff --git a/vendor/github.com/goccy/go-json/internal/encoder/compiler.go b/vendor/github.com/goccy/go-json/internal/encoder/compiler.go
index 3ae39ba8c..37b7aa38e 100644
--- a/vendor/github.com/goccy/go-json/internal/encoder/compiler.go
+++ b/vendor/github.com/goccy/go-json/internal/encoder/compiler.go
@@ -480,7 +480,7 @@ func (c *Compiler) mapCode(typ *runtime.Type) (*MapCode, error) {
 
 func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) {
 	switch {
-	case c.isPtrMarshalJSONType(typ):
+	case c.implementsMarshalJSONType(typ) || c.implementsMarshalJSONType(runtime.PtrTo(typ)):
 		return c.marshalJSONCode(typ)
 	case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType):
 		return c.marshalTextCode(typ)
diff --git a/vendor/github.com/goccy/go-json/internal/encoder/int.go b/vendor/github.com/goccy/go-json/internal/encoder/int.go
index 85f079609..8b5febeaa 100644
--- a/vendor/github.com/goccy/go-json/internal/encoder/int.go
+++ b/vendor/github.com/goccy/go-json/internal/encoder/int.go
@@ -1,3 +1,27 @@
+// This files's processing codes are inspired by https://github.com/segmentio/encoding.
+// The license notation is as follows.
+//
+// # MIT License
+//
+// Copyright (c) 2019 Segment.io, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
 package encoder
 
 import (
diff --git a/vendor/github.com/goccy/go-json/internal/encoder/string.go b/vendor/github.com/goccy/go-json/internal/encoder/string.go
index e4152b27c..4abb84165 100644
--- a/vendor/github.com/goccy/go-json/internal/encoder/string.go
+++ b/vendor/github.com/goccy/go-json/internal/encoder/string.go
@@ -1,3 +1,27 @@
+// This files's string processing codes are inspired by https://github.com/segmentio/encoding.
+// The license notation is as follows.
+//
+// # MIT License
+//
+// Copyright (c) 2019 Segment.io, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
 package encoder
 
 import (
diff --git a/vendor/github.com/goccy/go-json/internal/runtime/rtype.go b/vendor/github.com/goccy/go-json/internal/runtime/rtype.go
index 4db10debe..37cfe35a1 100644
--- a/vendor/github.com/goccy/go-json/internal/runtime/rtype.go
+++ b/vendor/github.com/goccy/go-json/internal/runtime/rtype.go
@@ -252,7 +252,6 @@ func IfaceIndir(*Type) bool
 //go:noescape
 func RType2Type(t *Type) reflect.Type
 
-//go:nolint structcheck
 type emptyInterface struct {
 	_   *Type
 	ptr unsafe.Pointer
diff --git a/vendor/github.com/goccy/go-json/json.go b/vendor/github.com/goccy/go-json/json.go
index 413cb20bf..fb18065a2 100644
--- a/vendor/github.com/goccy/go-json/json.go
+++ b/vendor/github.com/goccy/go-json/json.go
@@ -89,31 +89,31 @@ type UnmarshalerContext interface {
 //
 // Examples of struct field tags and their meanings:
 //
-//   // Field appears in JSON as key "myName".
-//   Field int `json:"myName"`
+//	// Field appears in JSON as key "myName".
+//	Field int `json:"myName"`
 //
-//   // Field appears in JSON as key "myName" and
-//   // the field is omitted from the object if its value is empty,
-//   // as defined above.
-//   Field int `json:"myName,omitempty"`
+//	// Field appears in JSON as key "myName" and
+//	// the field is omitted from the object if its value is empty,
+//	// as defined above.
+//	Field int `json:"myName,omitempty"`
 //
-//   // Field appears in JSON as key "Field" (the default), but
-//   // the field is skipped if empty.
-//   // Note the leading comma.
-//   Field int `json:",omitempty"`
+//	// Field appears in JSON as key "Field" (the default), but
+//	// the field is skipped if empty.
+//	// Note the leading comma.
+//	Field int `json:",omitempty"`
 //
-//   // Field is ignored by this package.
-//   Field int `json:"-"`
+//	// Field is ignored by this package.
+//	Field int `json:"-"`
 //
-//   // Field appears in JSON as key "-".
-//   Field int `json:"-,"`
+//	// Field appears in JSON as key "-".
+//	Field int `json:"-,"`
 //
 // The "string" option signals that a field is stored as JSON inside a
 // JSON-encoded string. It applies only to fields of string, floating point,
 // integer, or boolean types. This extra level of encoding is sometimes used
 // when communicating with JavaScript programs:
 //
-//    Int64String int64 `json:",string"`
+//	Int64String int64 `json:",string"`
 //
 // The key name will be used if it's a non-empty string consisting of
 // only Unicode letters, digits, and ASCII punctuation except quotation
@@ -166,7 +166,6 @@ type UnmarshalerContext interface {
 // JSON cannot represent cyclic data structures and Marshal does not
 // handle them. Passing cyclic structures to Marshal will result in
 // an infinite recursion.
-//
 func Marshal(v interface{}) ([]byte, error) {
 	return MarshalWithOption(v)
 }
@@ -264,14 +263,13 @@ func MarshalIndentWithOption(v interface{}, prefix, indent string, optFuncs ...E
 //
 // The JSON null value unmarshals into an interface, map, pointer, or slice
 // by setting that Go value to nil. Because null is often used in JSON to mean
-// ``not present,'' unmarshaling a JSON null into any other Go type has no effect
+// “not present,” unmarshaling a JSON null into any other Go type has no effect
 // on the value and produces no error.
 //
 // When unmarshaling quoted strings, invalid UTF-8 or
 // invalid UTF-16 surrogate pairs are not treated as an error.
 // Instead, they are replaced by the Unicode replacement
 // character U+FFFD.
-//
 func Unmarshal(data []byte, v interface{}) error {
 	return unmarshal(data, v)
 }
@@ -299,7 +297,6 @@ func UnmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc)
 //	Number, for JSON numbers
 //	string, for JSON string literals
 //	nil, for JSON null
-//
 type Token = json.Token
 
 // A Number represents a JSON number literal.
diff --git a/vendor/github.com/klauspost/compress/s2/decode_arm64.s b/vendor/github.com/klauspost/compress/s2/decode_arm64.s
index 4b63d5086..78e463f34 100644
--- a/vendor/github.com/klauspost/compress/s2/decode_arm64.s
+++ b/vendor/github.com/klauspost/compress/s2/decode_arm64.s
@@ -60,7 +60,7 @@
 //
 // The d variable is implicitly R_DST - R_DBASE,  and len(dst)-d is R_DEND - R_DST.
 // The s variable is implicitly R_SRC - R_SBASE, and len(src)-s is R_SEND - R_SRC.
-TEXT ·s2Decode(SB), NOSPLIT, $56-64
+TEXT ·s2Decode(SB), NOSPLIT, $56-56
 	// Initialize R_SRC, R_DST and R_DBASE-R_SEND.
 	MOVD dst_base+0(FP), R_DBASE
 	MOVD dst_len+8(FP), R_DLEN
diff --git a/vendor/github.com/klauspost/compress/s2/index.go b/vendor/github.com/klauspost/compress/s2/index.go
index 18a4f7acd..4229957b9 100644
--- a/vendor/github.com/klauspost/compress/s2/index.go
+++ b/vendor/github.com/klauspost/compress/s2/index.go
@@ -17,6 +17,8 @@ const (
 	S2IndexHeader   = "s2idx\x00"
 	S2IndexTrailer  = "\x00xdi2s"
 	maxIndexEntries = 1 << 16
+	// If distance is less than this, we do not add the entry.
+	minIndexDist = 1 << 20
 )
 
 // Index represents an S2/Snappy index.
@@ -72,6 +74,10 @@ func (i *Index) add(compressedOffset, uncompressedOffset int64) error {
 		if latest.compressedOffset > compressedOffset {
 			return fmt.Errorf("internal error: Earlier compressed received (%d > %d)", latest.uncompressedOffset, uncompressedOffset)
 		}
+		if latest.uncompressedOffset+minIndexDist > uncompressedOffset {
+			// Only add entry if distance is large enough.
+			return nil
+		}
 	}
 	i.info = append(i.info, struct {
 		compressedOffset   int64
@@ -122,7 +128,7 @@ func (i *Index) Find(offset int64) (compressedOff, uncompressedOff int64, err er
 
 // reduce to stay below maxIndexEntries
 func (i *Index) reduce() {
-	if len(i.info) < maxIndexEntries && i.estBlockUncomp >= 1<<20 {
+	if len(i.info) < maxIndexEntries && i.estBlockUncomp >= minIndexDist {
 		return
 	}
 
@@ -132,7 +138,7 @@ func (i *Index) reduce() {
 	j := 0
 
 	// Each block should be at least 1MB, but don't reduce below 1000 entries.
-	for i.estBlockUncomp*(int64(removeN)+1) < 1<<20 && len(i.info)/(removeN+1) > 1000 {
+	for i.estBlockUncomp*(int64(removeN)+1) < minIndexDist && len(i.info)/(removeN+1) > 1000 {
 		removeN++
 	}
 	for idx := 0; idx < len(src); idx++ {
diff --git a/vendor/github.com/klauspost/compress/s2/s2.go b/vendor/github.com/klauspost/compress/s2/s2.go
index 72bcb4945..cbd1ed64d 100644
--- a/vendor/github.com/klauspost/compress/s2/s2.go
+++ b/vendor/github.com/klauspost/compress/s2/s2.go
@@ -109,7 +109,11 @@ const (
 	chunkTypeStreamIdentifier = 0xff
 )
 
-var crcTable = crc32.MakeTable(crc32.Castagnoli)
+var (
+	crcTable              = crc32.MakeTable(crc32.Castagnoli)
+	magicChunkSnappyBytes = []byte(magicChunkSnappy) // Can be passed to functions where it escapes.
+	magicChunkBytes       = []byte(magicChunk)       // Can be passed to functions where it escapes.
+)
 
 // crc implements the checksum specified in section 3 of
 // https://github.com/google/snappy/blob/master/framing_format.txt
diff --git a/vendor/github.com/klauspost/compress/s2/writer.go b/vendor/github.com/klauspost/compress/s2/writer.go
index 637c93147..0a46f2b98 100644
--- a/vendor/github.com/klauspost/compress/s2/writer.go
+++ b/vendor/github.com/klauspost/compress/s2/writer.go
@@ -239,6 +239,9 @@ func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
 			}
 		}
 		if n2 == 0 {
+			if cap(inbuf) >= w.obufLen {
+				w.buffers.Put(inbuf)
+			}
 			break
 		}
 		n += int64(n2)
@@ -314,9 +317,9 @@ func (w *Writer) AddSkippableBlock(id uint8, data []byte) (err error) {
 		hWriter := make(chan result)
 		w.output <- hWriter
 		if w.snappy {
-			hWriter <- result{startOffset: w.uncompWritten, b: []byte(magicChunkSnappy)}
+			hWriter <- result{startOffset: w.uncompWritten, b: magicChunkSnappyBytes}
 		} else {
-			hWriter <- result{startOffset: w.uncompWritten, b: []byte(magicChunk)}
+			hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes}
 		}
 	}
 
@@ -370,9 +373,9 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) {
 		hWriter := make(chan result)
 		w.output <- hWriter
 		if w.snappy {
-			hWriter <- result{startOffset: w.uncompWritten, b: []byte(magicChunkSnappy)}
+			hWriter <- result{startOffset: w.uncompWritten, b: magicChunkSnappyBytes}
 		} else {
-			hWriter <- result{startOffset: w.uncompWritten, b: []byte(magicChunk)}
+			hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes}
 		}
 	}
 
@@ -478,9 +481,9 @@ func (w *Writer) write(p []byte) (nRet int, errRet error) {
 			hWriter := make(chan result)
 			w.output <- hWriter
 			if w.snappy {
-				hWriter <- result{startOffset: w.uncompWritten, b: []byte(magicChunkSnappy)}
+				hWriter <- result{startOffset: w.uncompWritten, b: magicChunkSnappyBytes}
 			} else {
-				hWriter <- result{startOffset: w.uncompWritten, b: []byte(magicChunk)}
+				hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes}
 			}
 		}
 
@@ -560,6 +563,9 @@ func (w *Writer) writeFull(inbuf []byte) (errRet error) {
 
 	if w.concurrency == 1 {
 		_, err := w.writeSync(inbuf[obufHeaderLen:])
+		if cap(inbuf) >= w.obufLen {
+			w.buffers.Put(inbuf)
+		}
 		return err
 	}
 
@@ -569,9 +575,9 @@ func (w *Writer) writeFull(inbuf []byte) (errRet error) {
 		hWriter := make(chan result)
 		w.output <- hWriter
 		if w.snappy {
-			hWriter <- result{startOffset: w.uncompWritten, b: []byte(magicChunkSnappy)}
+			hWriter <- result{startOffset: w.uncompWritten, b: magicChunkSnappyBytes}
 		} else {
-			hWriter <- result{startOffset: w.uncompWritten, b: []byte(magicChunk)}
+			hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes}
 		}
 	}
 
@@ -637,9 +643,9 @@ func (w *Writer) writeSync(p []byte) (nRet int, errRet error) {
 		var n int
 		var err error
 		if w.snappy {
-			n, err = w.writer.Write([]byte(magicChunkSnappy))
+			n, err = w.writer.Write(magicChunkSnappyBytes)
 		} else {
-			n, err = w.writer.Write([]byte(magicChunk))
+			n, err = w.writer.Write(magicChunkBytes)
 		}
 		if err != nil {
 			return 0, w.err(err)
diff --git a/vendor/github.com/klauspost/cpuid/v2/README.md b/vendor/github.com/klauspost/cpuid/v2/README.md
index 30f8d2963..21508edbd 100644
--- a/vendor/github.com/klauspost/cpuid/v2/README.md
+++ b/vendor/github.com/klauspost/cpuid/v2/README.md
@@ -310,6 +310,7 @@ Exit Code 1
 | AVXSLOW            | Indicates the CPU performs 2 128 bit operations instead of one                                                                                                                     |
 | AVXVNNI            | AVX (VEX encoded) VNNI neural network instructions                                                                                                                                 |
 | AVXVNNIINT8        | AVX-VNNI-INT8 instructions                                                                                                                                                         |
+| AVXVNNIINT16       | AVX-VNNI-INT16 instructions                                                                                                                                                        |
 | BHI_CTRL           | Branch History Injection and Intra-mode Branch Target Injection / CVE-2022-0001, CVE-2022-0002 / INTEL-SA-00598                                                                    |
 | BMI1               | Bit Manipulation Instruction Set 1                                                                                                                                                 |
 | BMI2               | Bit Manipulation Instruction Set 2                                                                                                                                                 |
diff --git a/vendor/github.com/klauspost/cpuid/v2/cpuid.go b/vendor/github.com/klauspost/cpuid/v2/cpuid.go
index 805f5e7b4..53bc18ca7 100644
--- a/vendor/github.com/klauspost/cpuid/v2/cpuid.go
+++ b/vendor/github.com/klauspost/cpuid/v2/cpuid.go
@@ -104,6 +104,7 @@ const (
 	AVXSLOW                              // Indicates the CPU performs 2 128 bit operations instead of one
 	AVXVNNI                              // AVX (VEX encoded) VNNI neural network instructions
 	AVXVNNIINT8                          // AVX-VNNI-INT8 instructions
+	AVXVNNIINT16                         // AVX-VNNI-INT16 instructions
 	BHI_CTRL                             // Branch History Injection and Intra-mode Branch Target Injection / CVE-2022-0001, CVE-2022-0002 / INTEL-SA-00598
 	BMI1                                 // Bit Manipulation Instruction Set 1
 	BMI2                                 // Bit Manipulation Instruction Set 2
@@ -1242,6 +1243,7 @@ func support() flagSet {
 		// CPUID.(EAX=7, ECX=1).EDX
 		fs.setIf(edx1&(1<<4) != 0, AVXVNNIINT8)
 		fs.setIf(edx1&(1<<5) != 0, AVXNECONVERT)
+		fs.setIf(edx1&(1<<10) != 0, AVXVNNIINT16)
 		fs.setIf(edx1&(1<<14) != 0, PREFETCHI)
 		fs.setIf(edx1&(1<<19) != 0, AVX10)
 		fs.setIf(edx1&(1<<21) != 0, APX_F)
diff --git a/vendor/github.com/klauspost/cpuid/v2/featureid_string.go b/vendor/github.com/klauspost/cpuid/v2/featureid_string.go
index 57a085a53..3a2560310 100644
--- a/vendor/github.com/klauspost/cpuid/v2/featureid_string.go
+++ b/vendor/github.com/klauspost/cpuid/v2/featureid_string.go
@@ -44,194 +44,195 @@ func _() {
 	_ = x[AVXSLOW-34]
 	_ = x[AVXVNNI-35]
 	_ = x[AVXVNNIINT8-36]
-	_ = x[BHI_CTRL-37]
-	_ = x[BMI1-38]
-	_ = x[BMI2-39]
-	_ = x[CETIBT-40]
-	_ = x[CETSS-41]
-	_ = x[CLDEMOTE-42]
-	_ = x[CLMUL-43]
-	_ = x[CLZERO-44]
-	_ = x[CMOV-45]
-	_ = x[CMPCCXADD-46]
-	_ = x[CMPSB_SCADBS_SHORT-47]
-	_ = x[CMPXCHG8-48]
-	_ = x[CPBOOST-49]
-	_ = x[CPPC-50]
-	_ = x[CX16-51]
-	_ = x[EFER_LMSLE_UNS-52]
-	_ = x[ENQCMD-53]
-	_ = x[ERMS-54]
-	_ = x[F16C-55]
-	_ = x[FLUSH_L1D-56]
-	_ = x[FMA3-57]
-	_ = x[FMA4-58]
-	_ = x[FP128-59]
-	_ = x[FP256-60]
-	_ = x[FSRM-61]
-	_ = x[FXSR-62]
-	_ = x[FXSROPT-63]
-	_ = x[GFNI-64]
-	_ = x[HLE-65]
-	_ = x[HRESET-66]
-	_ = x[HTT-67]
-	_ = x[HWA-68]
-	_ = x[HYBRID_CPU-69]
-	_ = x[HYPERVISOR-70]
-	_ = x[IA32_ARCH_CAP-71]
-	_ = x[IA32_CORE_CAP-72]
-	_ = x[IBPB-73]
-	_ = x[IBPB_BRTYPE-74]
-	_ = x[IBRS-75]
-	_ = x[IBRS_PREFERRED-76]
-	_ = x[IBRS_PROVIDES_SMP-77]
-	_ = x[IBS-78]
-	_ = x[IBSBRNTRGT-79]
-	_ = x[IBSFETCHSAM-80]
-	_ = x[IBSFFV-81]
-	_ = x[IBSOPCNT-82]
-	_ = x[IBSOPCNTEXT-83]
-	_ = x[IBSOPSAM-84]
-	_ = x[IBSRDWROPCNT-85]
-	_ = x[IBSRIPINVALIDCHK-86]
-	_ = x[IBS_FETCH_CTLX-87]
-	_ = x[IBS_OPDATA4-88]
-	_ = x[IBS_OPFUSE-89]
-	_ = x[IBS_PREVENTHOST-90]
-	_ = x[IBS_ZEN4-91]
-	_ = x[IDPRED_CTRL-92]
-	_ = x[INT_WBINVD-93]
-	_ = x[INVLPGB-94]
-	_ = x[KEYLOCKER-95]
-	_ = x[KEYLOCKERW-96]
-	_ = x[LAHF-97]
-	_ = x[LAM-98]
-	_ = x[LBRVIRT-99]
-	_ = x[LZCNT-100]
-	_ = x[MCAOVERFLOW-101]
-	_ = x[MCDT_NO-102]
-	_ = x[MCOMMIT-103]
-	_ = x[MD_CLEAR-104]
-	_ = x[MMX-105]
-	_ = x[MMXEXT-106]
-	_ = x[MOVBE-107]
-	_ = x[MOVDIR64B-108]
-	_ = x[MOVDIRI-109]
-	_ = x[MOVSB_ZL-110]
-	_ = x[MOVU-111]
-	_ = x[MPX-112]
-	_ = x[MSRIRC-113]
-	_ = x[MSRLIST-114]
-	_ = x[MSR_PAGEFLUSH-115]
-	_ = x[NRIPS-116]
-	_ = x[NX-117]
-	_ = x[OSXSAVE-118]
-	_ = x[PCONFIG-119]
-	_ = x[POPCNT-120]
-	_ = x[PPIN-121]
-	_ = x[PREFETCHI-122]
-	_ = x[PSFD-123]
-	_ = x[RDPRU-124]
-	_ = x[RDRAND-125]
-	_ = x[RDSEED-126]
-	_ = x[RDTSCP-127]
-	_ = x[RRSBA_CTRL-128]
-	_ = x[RTM-129]
-	_ = x[RTM_ALWAYS_ABORT-130]
-	_ = x[SBPB-131]
-	_ = x[SERIALIZE-132]
-	_ = x[SEV-133]
-	_ = x[SEV_64BIT-134]
-	_ = x[SEV_ALTERNATIVE-135]
-	_ = x[SEV_DEBUGSWAP-136]
-	_ = x[SEV_ES-137]
-	_ = x[SEV_RESTRICTED-138]
-	_ = x[SEV_SNP-139]
-	_ = x[SGX-140]
-	_ = x[SGXLC-141]
-	_ = x[SHA-142]
-	_ = x[SME-143]
-	_ = x[SME_COHERENT-144]
-	_ = x[SPEC_CTRL_SSBD-145]
-	_ = x[SRBDS_CTRL-146]
-	_ = x[SRSO_MSR_FIX-147]
-	_ = x[SRSO_NO-148]
-	_ = x[SRSO_USER_KERNEL_NO-149]
-	_ = x[SSE-150]
-	_ = x[SSE2-151]
-	_ = x[SSE3-152]
-	_ = x[SSE4-153]
-	_ = x[SSE42-154]
-	_ = x[SSE4A-155]
-	_ = x[SSSE3-156]
-	_ = x[STIBP-157]
-	_ = x[STIBP_ALWAYSON-158]
-	_ = x[STOSB_SHORT-159]
-	_ = x[SUCCOR-160]
-	_ = x[SVM-161]
-	_ = x[SVMDA-162]
-	_ = x[SVMFBASID-163]
-	_ = x[SVML-164]
-	_ = x[SVMNP-165]
-	_ = x[SVMPF-166]
-	_ = x[SVMPFT-167]
-	_ = x[SYSCALL-168]
-	_ = x[SYSEE-169]
-	_ = x[TBM-170]
-	_ = x[TDX_GUEST-171]
-	_ = x[TLB_FLUSH_NESTED-172]
-	_ = x[TME-173]
-	_ = x[TOPEXT-174]
-	_ = x[TSCRATEMSR-175]
-	_ = x[TSXLDTRK-176]
-	_ = x[VAES-177]
-	_ = x[VMCBCLEAN-178]
-	_ = x[VMPL-179]
-	_ = x[VMSA_REGPROT-180]
-	_ = x[VMX-181]
-	_ = x[VPCLMULQDQ-182]
-	_ = x[VTE-183]
-	_ = x[WAITPKG-184]
-	_ = x[WBNOINVD-185]
-	_ = x[WRMSRNS-186]
-	_ = x[X87-187]
-	_ = x[XGETBV1-188]
-	_ = x[XOP-189]
-	_ = x[XSAVE-190]
-	_ = x[XSAVEC-191]
-	_ = x[XSAVEOPT-192]
-	_ = x[XSAVES-193]
-	_ = x[AESARM-194]
-	_ = x[ARMCPUID-195]
-	_ = x[ASIMD-196]
-	_ = x[ASIMDDP-197]
-	_ = x[ASIMDHP-198]
-	_ = x[ASIMDRDM-199]
-	_ = x[ATOMICS-200]
-	_ = x[CRC32-201]
-	_ = x[DCPOP-202]
-	_ = x[EVTSTRM-203]
-	_ = x[FCMA-204]
-	_ = x[FP-205]
-	_ = x[FPHP-206]
-	_ = x[GPA-207]
-	_ = x[JSCVT-208]
-	_ = x[LRCPC-209]
-	_ = x[PMULL-210]
-	_ = x[SHA1-211]
-	_ = x[SHA2-212]
-	_ = x[SHA3-213]
-	_ = x[SHA512-214]
-	_ = x[SM3-215]
-	_ = x[SM4-216]
-	_ = x[SVE-217]
-	_ = x[lastID-218]
+	_ = x[AVXVNNIINT16-37]
+	_ = x[BHI_CTRL-38]
+	_ = x[BMI1-39]
+	_ = x[BMI2-40]
+	_ = x[CETIBT-41]
+	_ = x[CETSS-42]
+	_ = x[CLDEMOTE-43]
+	_ = x[CLMUL-44]
+	_ = x[CLZERO-45]
+	_ = x[CMOV-46]
+	_ = x[CMPCCXADD-47]
+	_ = x[CMPSB_SCADBS_SHORT-48]
+	_ = x[CMPXCHG8-49]
+	_ = x[CPBOOST-50]
+	_ = x[CPPC-51]
+	_ = x[CX16-52]
+	_ = x[EFER_LMSLE_UNS-53]
+	_ = x[ENQCMD-54]
+	_ = x[ERMS-55]
+	_ = x[F16C-56]
+	_ = x[FLUSH_L1D-57]
+	_ = x[FMA3-58]
+	_ = x[FMA4-59]
+	_ = x[FP128-60]
+	_ = x[FP256-61]
+	_ = x[FSRM-62]
+	_ = x[FXSR-63]
+	_ = x[FXSROPT-64]
+	_ = x[GFNI-65]
+	_ = x[HLE-66]
+	_ = x[HRESET-67]
+	_ = x[HTT-68]
+	_ = x[HWA-69]
+	_ = x[HYBRID_CPU-70]
+	_ = x[HYPERVISOR-71]
+	_ = x[IA32_ARCH_CAP-72]
+	_ = x[IA32_CORE_CAP-73]
+	_ = x[IBPB-74]
+	_ = x[IBPB_BRTYPE-75]
+	_ = x[IBRS-76]
+	_ = x[IBRS_PREFERRED-77]
+	_ = x[IBRS_PROVIDES_SMP-78]
+	_ = x[IBS-79]
+	_ = x[IBSBRNTRGT-80]
+	_ = x[IBSFETCHSAM-81]
+	_ = x[IBSFFV-82]
+	_ = x[IBSOPCNT-83]
+	_ = x[IBSOPCNTEXT-84]
+	_ = x[IBSOPSAM-85]
+	_ = x[IBSRDWROPCNT-86]
+	_ = x[IBSRIPINVALIDCHK-87]
+	_ = x[IBS_FETCH_CTLX-88]
+	_ = x[IBS_OPDATA4-89]
+	_ = x[IBS_OPFUSE-90]
+	_ = x[IBS_PREVENTHOST-91]
+	_ = x[IBS_ZEN4-92]
+	_ = x[IDPRED_CTRL-93]
+	_ = x[INT_WBINVD-94]
+	_ = x[INVLPGB-95]
+	_ = x[KEYLOCKER-96]
+	_ = x[KEYLOCKERW-97]
+	_ = x[LAHF-98]
+	_ = x[LAM-99]
+	_ = x[LBRVIRT-100]
+	_ = x[LZCNT-101]
+	_ = x[MCAOVERFLOW-102]
+	_ = x[MCDT_NO-103]
+	_ = x[MCOMMIT-104]
+	_ = x[MD_CLEAR-105]
+	_ = x[MMX-106]
+	_ = x[MMXEXT-107]
+	_ = x[MOVBE-108]
+	_ = x[MOVDIR64B-109]
+	_ = x[MOVDIRI-110]
+	_ = x[MOVSB_ZL-111]
+	_ = x[MOVU-112]
+	_ = x[MPX-113]
+	_ = x[MSRIRC-114]
+	_ = x[MSRLIST-115]
+	_ = x[MSR_PAGEFLUSH-116]
+	_ = x[NRIPS-117]
+	_ = x[NX-118]
+	_ = x[OSXSAVE-119]
+	_ = x[PCONFIG-120]
+	_ = x[POPCNT-121]
+	_ = x[PPIN-122]
+	_ = x[PREFETCHI-123]
+	_ = x[PSFD-124]
+	_ = x[RDPRU-125]
+	_ = x[RDRAND-126]
+	_ = x[RDSEED-127]
+	_ = x[RDTSCP-128]
+	_ = x[RRSBA_CTRL-129]
+	_ = x[RTM-130]
+	_ = x[RTM_ALWAYS_ABORT-131]
+	_ = x[SBPB-132]
+	_ = x[SERIALIZE-133]
+	_ = x[SEV-134]
+	_ = x[SEV_64BIT-135]
+	_ = x[SEV_ALTERNATIVE-136]
+	_ = x[SEV_DEBUGSWAP-137]
+	_ = x[SEV_ES-138]
+	_ = x[SEV_RESTRICTED-139]
+	_ = x[SEV_SNP-140]
+	_ = x[SGX-141]
+	_ = x[SGXLC-142]
+	_ = x[SHA-143]
+	_ = x[SME-144]
+	_ = x[SME_COHERENT-145]
+	_ = x[SPEC_CTRL_SSBD-146]
+	_ = x[SRBDS_CTRL-147]
+	_ = x[SRSO_MSR_FIX-148]
+	_ = x[SRSO_NO-149]
+	_ = x[SRSO_USER_KERNEL_NO-150]
+	_ = x[SSE-151]
+	_ = x[SSE2-152]
+	_ = x[SSE3-153]
+	_ = x[SSE4-154]
+	_ = x[SSE42-155]
+	_ = x[SSE4A-156]
+	_ = x[SSSE3-157]
+	_ = x[STIBP-158]
+	_ = x[STIBP_ALWAYSON-159]
+	_ = x[STOSB_SHORT-160]
+	_ = x[SUCCOR-161]
+	_ = x[SVM-162]
+	_ = x[SVMDA-163]
+	_ = x[SVMFBASID-164]
+	_ = x[SVML-165]
+	_ = x[SVMNP-166]
+	_ = x[SVMPF-167]
+	_ = x[SVMPFT-168]
+	_ = x[SYSCALL-169]
+	_ = x[SYSEE-170]
+	_ = x[TBM-171]
+	_ = x[TDX_GUEST-172]
+	_ = x[TLB_FLUSH_NESTED-173]
+	_ = x[TME-174]
+	_ = x[TOPEXT-175]
+	_ = x[TSCRATEMSR-176]
+	_ = x[TSXLDTRK-177]
+	_ = x[VAES-178]
+	_ = x[VMCBCLEAN-179]
+	_ = x[VMPL-180]
+	_ = x[VMSA_REGPROT-181]
+	_ = x[VMX-182]
+	_ = x[VPCLMULQDQ-183]
+	_ = x[VTE-184]
+	_ = x[WAITPKG-185]
+	_ = x[WBNOINVD-186]
+	_ = x[WRMSRNS-187]
+	_ = x[X87-188]
+	_ = x[XGETBV1-189]
+	_ = x[XOP-190]
+	_ = x[XSAVE-191]
+	_ = x[XSAVEC-192]
+	_ = x[XSAVEOPT-193]
+	_ = x[XSAVES-194]
+	_ = x[AESARM-195]
+	_ = x[ARMCPUID-196]
+	_ = x[ASIMD-197]
+	_ = x[ASIMDDP-198]
+	_ = x[ASIMDHP-199]
+	_ = x[ASIMDRDM-200]
+	_ = x[ATOMICS-201]
+	_ = x[CRC32-202]
+	_ = x[DCPOP-203]
+	_ = x[EVTSTRM-204]
+	_ = x[FCMA-205]
+	_ = x[FP-206]
+	_ = x[FPHP-207]
+	_ = x[GPA-208]
+	_ = x[JSCVT-209]
+	_ = x[LRCPC-210]
+	_ = x[PMULL-211]
+	_ = x[SHA1-212]
+	_ = x[SHA2-213]
+	_ = x[SHA3-214]
+	_ = x[SHA512-215]
+	_ = x[SM3-216]
+	_ = x[SM4-217]
+	_ = x[SVE-218]
+	_ = x[lastID-219]
 	_ = x[firstID-0]
 }
 
-const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXFP16AMXINT8AMXTILEAPX_FAVXAVX10AVX10_128AVX10_256AVX10_512AVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXIFMAAVXNECONVERTAVXSLOWAVXVNNIAVXVNNIINT8BHI_CTRLBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPCCXADDCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCPPCCX16EFER_LMSLE_UNSENQCMDERMSF16CFLUSH_L1DFMA3FMA4FP128FP256FSRMFXSRFXSROPTGFNIHLEHRESETHTTHWAHYBRID_CPUHYPERVISORIA32_ARCH_CAPIA32_CORE_CAPIBPBIBPB_BRTYPEIBRSIBRS_PREFERREDIBRS_PROVIDES_SMPIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_FETCH_CTLXIBS_OPDATA4IBS_OPFUSEIBS_PREVENTHOSTIBS_ZEN4IDPRED_CTRLINT_WBINVDINVLPGBKEYLOCKERKEYLOCKERWLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCDT_NOMCOMMITMD_CLEARMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMOVUMPXMSRIRCMSRLISTMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTPPINPREFETCHIPSFDRDPRURDRANDRDSEEDRDTSCPRRSBA_CTRLRTMRTM_ALWAYS_ABORTSBPBSERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSHASMESME_COHERENTSPEC_CTRL_SSBDSRBDS_CTRLSRSO_MSR_FIXSRSO_NOSRSO_USER_KERNEL_NOSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTIBP_ALWAYSONSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTSYSCALLSYSEETBMTDX_GUESTTLB_FLUSH_NESTEDTMETOPEXTTSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDWRMSRNSX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID"
+const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXFP16AMXINT8AMXTILEAPX_FAVXAVX10AVX10_128AVX10_256AVX10_512AVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXIFMAAVXNECONVERTAVXSLOWAVXVNNIAVXVNNIINT8AVXVNNIINT16BHI_CTRLBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPCCXADDCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCPPCCX16EFER_LMSLE_UNSENQCMDERMSF16CFLUSH_L1DFMA3FMA4FP128FP256FSRMFXSRFXSROPTGFNIHLEHRESETHTTHWAHYBRID_CPUHYPERVISORIA32_ARCH_CAPIA32_CORE_CAPIBPBIBPB_BRTYPEIBRSIBRS_PREFERREDIBRS_PROVIDES_SMPIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_FETCH_CTLXIBS_OPDATA4IBS_OPFUSEIBS_PREVENTHOSTIBS_ZEN4IDPRED_CTRLINT_WBINVDINVLPGBKEYLOCKERKEYLOCKERWLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCDT_NOMCOMMITMD_CLEARMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMOVUMPXMSRIRCMSRLISTMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTPPINPREFETCHIPSFDRDPRURDRANDRDSEEDRDTSCPRRSBA_CTRLRTMRTM_ALWAYS_ABORTSBPBSERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSHASMESME_COHERENTSPEC_CTRL_SSBDSRBDS_CTRLSRSO_MSR_FIXSRSO_NOSRSO_USER_KERNEL_NOSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTIBP_ALWAYSONSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTSYSCALLSYSEETBMTDX_GUESTTLB_FLUSH_NESTEDTMETOPEXTTSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDWRMSRNSX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID"
 
-var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 62, 67, 70, 75, 84, 93, 102, 106, 116, 128, 136, 144, 152, 160, 167, 177, 187, 195, 205, 216, 224, 234, 252, 267, 274, 286, 293, 300, 311, 319, 323, 327, 333, 338, 346, 351, 357, 361, 370, 388, 396, 403, 407, 411, 425, 431, 435, 439, 448, 452, 456, 461, 466, 470, 474, 481, 485, 488, 494, 497, 500, 510, 520, 533, 546, 550, 561, 565, 579, 596, 599, 609, 620, 626, 634, 645, 653, 665, 681, 695, 706, 716, 731, 739, 750, 760, 767, 776, 786, 790, 793, 800, 805, 816, 823, 830, 838, 841, 847, 852, 861, 868, 876, 880, 883, 889, 896, 909, 914, 916, 923, 930, 936, 940, 949, 953, 958, 964, 970, 976, 986, 989, 1005, 1009, 1018, 1021, 1030, 1045, 1058, 1064, 1078, 1085, 1088, 1093, 1096, 1099, 1111, 1125, 1135, 1147, 1154, 1173, 1176, 1180, 1184, 1188, 1193, 1198, 1203, 1208, 1222, 1233, 1239, 1242, 1247, 1256, 1260, 1265, 1270, 1276, 1283, 1288, 1291, 1300, 1316, 1319, 1325, 1335, 1343, 1347, 1356, 1360, 1372, 1375, 1385, 1388, 1395, 1403, 1410, 1413, 1420, 1423, 1428, 1434, 1442, 1448, 1454, 1462, 1467, 1474, 1481, 1489, 1496, 1501, 1506, 1513, 1517, 1519, 1523, 1526, 1531, 1536, 1541, 1545, 1549, 1553, 1559, 1562, 1565, 1568, 1574}
+var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 62, 67, 70, 75, 84, 93, 102, 106, 116, 128, 136, 144, 152, 160, 167, 177, 187, 195, 205, 216, 224, 234, 252, 267, 274, 286, 293, 300, 311, 323, 331, 335, 339, 345, 350, 358, 363, 369, 373, 382, 400, 408, 415, 419, 423, 437, 443, 447, 451, 460, 464, 468, 473, 478, 482, 486, 493, 497, 500, 506, 509, 512, 522, 532, 545, 558, 562, 573, 577, 591, 608, 611, 621, 632, 638, 646, 657, 665, 677, 693, 707, 718, 728, 743, 751, 762, 772, 779, 788, 798, 802, 805, 812, 817, 828, 835, 842, 850, 853, 859, 864, 873, 880, 888, 892, 895, 901, 908, 921, 926, 928, 935, 942, 948, 952, 961, 965, 970, 976, 982, 988, 998, 1001, 1017, 1021, 1030, 1033, 1042, 1057, 1070, 1076, 1090, 1097, 1100, 1105, 1108, 1111, 1123, 1137, 1147, 1159, 1166, 1185, 1188, 1192, 1196, 1200, 1205, 1210, 1215, 1220, 1234, 1245, 1251, 1254, 1259, 1268, 1272, 1277, 1282, 1288, 1295, 1300, 1303, 1312, 1328, 1331, 1337, 1347, 1355, 1359, 1368, 1372, 1384, 1387, 1397, 1400, 1407, 1415, 1422, 1425, 1432, 1435, 1440, 1446, 1454, 1460, 1466, 1474, 1479, 1486, 1493, 1501, 1508, 1513, 1518, 1525, 1529, 1531, 1535, 1538, 1543, 1548, 1553, 1557, 1561, 1565, 1571, 1574, 1577, 1580, 1586}
 
 func (i FeatureID) String() string {
 	if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) {
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go b/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go
index 9182d4eac..51226630d 100644
--- a/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go
+++ b/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go
@@ -637,7 +637,9 @@ func (c *Client) putObjectMultipartStreamParallel(ctx context.Context, bucketNam
 	// Sort all completed parts.
 	sort.Sort(completedParts(complMultipartUpload.Parts))
 
-	opts = PutObjectOptions{}
+	opts = PutObjectOptions{
+		ServerSideEncryption: opts.ServerSideEncryption,
+	}
 	if len(crcBytes) > 0 {
 		// Add hash of hashes.
 		crc.Reset()
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object.go b/vendor/github.com/minio/minio-go/v7/api-put-object.go
index a96de9b9f..6ccb58156 100644
--- a/vendor/github.com/minio/minio-go/v7/api-put-object.go
+++ b/vendor/github.com/minio/minio-go/v7/api-put-object.go
@@ -464,7 +464,9 @@ func (c *Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketNam
 	// Sort all completed parts.
 	sort.Sort(completedParts(complMultipartUpload.Parts))
 
-	opts = PutObjectOptions{}
+	opts = PutObjectOptions{
+		ServerSideEncryption: opts.ServerSideEncryption,
+	}
 	if len(crcBytes) > 0 {
 		// Add hash of hashes.
 		crc.Reset()
diff --git a/vendor/github.com/minio/minio-go/v7/api.go b/vendor/github.com/minio/minio-go/v7/api.go
index eaaaa68c2..602722782 100644
--- a/vendor/github.com/minio/minio-go/v7/api.go
+++ b/vendor/github.com/minio/minio-go/v7/api.go
@@ -129,7 +129,7 @@ type Options struct {
 // Global constants.
 const (
 	libraryName    = "minio-go"
-	libraryVersion = "v7.0.72"
+	libraryVersion = "v7.0.73"
 )
 
 // User Agent should always following the below style.
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_aws_credentials.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_aws_credentials.go
index 8c5c4eb2d..541e1a72f 100644
--- a/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_aws_credentials.go
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_aws_credentials.go
@@ -26,7 +26,7 @@ import (
 	"strings"
 	"time"
 
-	ini "gopkg.in/ini.v1"
+	"github.com/go-ini/ini"
 )
 
 // A externalProcessCredentials stores the output of a credential_process
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/replication/replication.go b/vendor/github.com/minio/minio-go/v7/pkg/replication/replication.go
index 0abbf6efc..65a2f75e9 100644
--- a/vendor/github.com/minio/minio-go/v7/pkg/replication/replication.go
+++ b/vendor/github.com/minio/minio-go/v7/pkg/replication/replication.go
@@ -406,6 +406,9 @@ func (c *Config) EditRule(opts Options) error {
 			return fmt.Errorf("priority must be unique. Replication configuration already has a rule with this priority")
 		}
 		if rule.Destination.Bucket != newRule.Destination.Bucket && rule.ID == newRule.ID {
+			if c.Role == newRule.Destination.Bucket {
+				continue
+			}
 			return fmt.Errorf("invalid destination bucket for this rule")
 		}
 	}
diff --git a/vendor/github.com/minio/minio-go/v7/s3-endpoints.go b/vendor/github.com/minio/minio-go/v7/s3-endpoints.go
index 068a6bfa1..01cee8a19 100644
--- a/vendor/github.com/minio/minio-go/v7/s3-endpoints.go
+++ b/vendor/github.com/minio/minio-go/v7/s3-endpoints.go
@@ -44,6 +44,10 @@ var awsS3EndpointMap = map[string]awsS3Endpoint{
 		"s3.ca-central-1.amazonaws.com",
 		"s3.dualstack.ca-central-1.amazonaws.com",
 	},
+	"ca-west-1": {
+		"s3.ca-west-1.amazonaws.com",
+		"s3.dualstack.ca-west-1.amazonaws.com",
+	},
 	"eu-west-1": {
 		"s3.eu-west-1.amazonaws.com",
 		"s3.dualstack.eu-west-1.amazonaws.com",
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 028bae6a5..a001cb3d7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -238,6 +238,9 @@ github.com/go-errors/errors
 # github.com/go-fed/httpsig v1.1.0
 ## explicit; go 1.13
 github.com/go-fed/httpsig
+# github.com/go-ini/ini v1.67.0
+## explicit
+github.com/go-ini/ini
 # github.com/go-jose/go-jose/v4 v4.0.1
 ## explicit; go 1.21
 github.com/go-jose/go-jose/v4
@@ -322,8 +325,8 @@ github.com/go-swagger/go-swagger/generator
 # github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850
 ## explicit
 github.com/go-xmlfmt/xmlfmt
-# github.com/goccy/go-json v0.10.2
-## explicit; go 1.12
+# github.com/goccy/go-json v0.10.3
+## explicit; go 1.19
 github.com/goccy/go-json
 github.com/goccy/go-json/internal/decoder
 github.com/goccy/go-json/internal/encoder
@@ -450,11 +453,11 @@ github.com/josharian/intern
 # github.com/json-iterator/go v1.1.12
 ## explicit; go 1.12
 github.com/json-iterator/go
-# github.com/klauspost/compress v1.17.8
+# github.com/klauspost/compress v1.17.9
 ## explicit; go 1.20
 github.com/klauspost/compress/internal/race
 github.com/klauspost/compress/s2
-# github.com/klauspost/cpuid/v2 v2.2.7
+# github.com/klauspost/cpuid/v2 v2.2.8
 ## explicit; go 1.15
 github.com/klauspost/cpuid/v2
 # github.com/kr/pretty v0.3.1
@@ -488,7 +491,7 @@ github.com/miekg/dns
 # github.com/minio/md5-simd v1.1.2
 ## explicit; go 1.14
 github.com/minio/md5-simd
-# github.com/minio/minio-go/v7 v7.0.72
+# github.com/minio/minio-go/v7 v7.0.73
 ## explicit; go 1.21
 github.com/minio/minio-go/v7
 github.com/minio/minio-go/v7/pkg/credentials