mirror of https://github.com/k3s-io/k3s
Update heketi dependencies to sha@558b29266ce0a873991ecfb3edc41a668a998514.
Fixes # https://github.com/kubernetes/kubernetes/issues/70802 Signed-off-by: Humble Chirammal <hchiramm@redhat.com>pull/58/head
parent
ea74b0b59d
commit
92fa290ead
|
@ -1639,6 +1639,16 @@
|
||||||
"Comment": "v0.17.2",
|
"Comment": "v0.17.2",
|
||||||
"Rev": "d2eab7d93009e9215fc85b2faa2c2f2a98c2af48"
|
"Rev": "d2eab7d93009e9215fc85b2faa2c2f2a98c2af48"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/go-ozzo/ozzo-validation",
|
||||||
|
"Comment": "v3.5.0",
|
||||||
|
"Rev": "106681dbb37bfa3e7683c4c8129cb7f5925ea3e9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/go-ozzo/ozzo-validation/is",
|
||||||
|
"Comment": "v3.5.0",
|
||||||
|
"Rev": "106681dbb37bfa3e7683c4c8129cb7f5925ea3e9"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||||
"Comment": "v1.3.0",
|
"Comment": "v1.3.0",
|
||||||
|
@ -2285,18 +2295,18 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/heketi/heketi/client/api/go-client",
|
"ImportPath": "github.com/heketi/heketi/client/api/go-client",
|
||||||
"Comment": "v4.0.0-95-gaaf40619d85fda",
|
"Comment": "v8.0.0-49-g558b29266ce0a8",
|
||||||
"Rev": "aaf40619d85fda757e7a1c1ea1b5118cea65594b"
|
"Rev": "558b29266ce0a873991ecfb3edc41a668a998514"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/heketi/heketi/pkg/glusterfs/api",
|
"ImportPath": "github.com/heketi/heketi/pkg/glusterfs/api",
|
||||||
"Comment": "v4.0.0-95-gaaf40619d85fda",
|
"Comment": "v8.0.0-49-g558b29266ce0a8",
|
||||||
"Rev": "aaf40619d85fda757e7a1c1ea1b5118cea65594b"
|
"Rev": "558b29266ce0a873991ecfb3edc41a668a998514"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/heketi/heketi/pkg/utils",
|
"ImportPath": "github.com/heketi/heketi/pkg/utils",
|
||||||
"Comment": "v4.0.0-95-gaaf40619d85fda",
|
"Comment": "v8.0.0-49-g558b29266ce0a8",
|
||||||
"Rev": "aaf40619d85fda757e7a1c1ea1b5118cea65594b"
|
"Rev": "558b29266ce0a873991ecfb3edc41a668a998514"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/imdario/mergo",
|
"ImportPath": "github.com/imdario/mergo",
|
||||||
|
@ -2408,11 +2418,6 @@
|
||||||
"ImportPath": "github.com/libopenstorage/openstorage/volume",
|
"ImportPath": "github.com/libopenstorage/openstorage/volume",
|
||||||
"Rev": "093a0c3888753c2056e7373183693d670c6bba01"
|
"Rev": "093a0c3888753c2056e7373183693d670c6bba01"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/lpabon/godbc",
|
|
||||||
"Comment": "v1.0-1-g9577782540c139",
|
|
||||||
"Rev": "9577782540c1398b710ddae1b86268ba03a19b0c"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/magiconair/properties",
|
"ImportPath": "github.com/magiconair/properties",
|
||||||
"Comment": "v1.7.0-4-g61b492c03cf472",
|
"Comment": "v1.7.0-4-g61b492c03cf472",
|
||||||
|
|
|
@ -48780,6 +48780,56 @@ third-party archives.
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
= vendor/github.com/go-ozzo/ozzo-validation licensed under: =
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
Copyright (c) 2016, Qiang Xue
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
= vendor/github.com/go-ozzo/ozzo-validation/LICENSE da12d993f2ce14947ad6eec35520b081
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
= vendor/github.com/go-ozzo/ozzo-validation/is licensed under: =
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
Copyright (c) 2016, Qiang Xue
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
= vendor/github.com/go-ozzo/ozzo-validation/LICENSE da12d993f2ce14947ad6eec35520b081
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
= vendor/github.com/go-sql-driver/mysql licensed under: =
|
= vendor/github.com/go-sql-driver/mysql licensed under: =
|
||||||
|
|
||||||
|
@ -74190,214 +74240,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
= vendor/github.com/lpabon/godbc licensed under: =
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) 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
|
|
||||||
|
|
||||||
(d) 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 {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
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.
|
|
||||||
= vendor/github.com/lpabon/godbc/LICENSE 6c4db32a2fa8717faffa1d4f10136f47
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
= vendor/github.com/magiconair/properties licensed under: =
|
= vendor/github.com/magiconair/properties licensed under: =
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,7 @@ filegroup(
|
||||||
"//vendor/github.com/go-openapi/strfmt:all-srcs",
|
"//vendor/github.com/go-openapi/strfmt:all-srcs",
|
||||||
"//vendor/github.com/go-openapi/swag:all-srcs",
|
"//vendor/github.com/go-openapi/swag:all-srcs",
|
||||||
"//vendor/github.com/go-openapi/validate:all-srcs",
|
"//vendor/github.com/go-openapi/validate:all-srcs",
|
||||||
|
"//vendor/github.com/go-ozzo/ozzo-validation:all-srcs",
|
||||||
"//vendor/github.com/go-sql-driver/mysql:all-srcs",
|
"//vendor/github.com/go-sql-driver/mysql:all-srcs",
|
||||||
"//vendor/github.com/godbus/dbus:all-srcs",
|
"//vendor/github.com/godbus/dbus:all-srcs",
|
||||||
"//vendor/github.com/gogo/protobuf/gogoproto:all-srcs",
|
"//vendor/github.com/gogo/protobuf/gogoproto:all-srcs",
|
||||||
|
@ -296,7 +297,6 @@ filegroup(
|
||||||
"//vendor/github.com/libopenstorage/openstorage/pkg/parser:all-srcs",
|
"//vendor/github.com/libopenstorage/openstorage/pkg/parser:all-srcs",
|
||||||
"//vendor/github.com/libopenstorage/openstorage/pkg/units:all-srcs",
|
"//vendor/github.com/libopenstorage/openstorage/pkg/units:all-srcs",
|
||||||
"//vendor/github.com/libopenstorage/openstorage/volume:all-srcs",
|
"//vendor/github.com/libopenstorage/openstorage/volume:all-srcs",
|
||||||
"//vendor/github.com/lpabon/godbc:all-srcs",
|
|
||||||
"//vendor/github.com/magiconair/properties:all-srcs",
|
"//vendor/github.com/magiconair/properties:all-srcs",
|
||||||
"//vendor/github.com/mailru/easyjson/buffer:all-srcs",
|
"//vendor/github.com/mailru/easyjson/buffer:all-srcs",
|
||||||
"//vendor/github.com/mailru/easyjson/jlexer:all-srcs",
|
"//vendor/github.com/mailru/easyjson/jlexer:all-srcs",
|
||||||
|
|
|
@ -21,3 +21,5 @@ _testmain.go
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
|
*.prof
|
||||||
|
.DS_Store
|
|
@ -0,0 +1,16 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8
|
||||||
|
- 1.9
|
||||||
|
- tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v
|
||||||
|
- go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -covermode=count -coverprofile=coverage.out
|
||||||
|
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
|
|
@ -0,0 +1,41 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"date.go",
|
||||||
|
"error.go",
|
||||||
|
"in.go",
|
||||||
|
"length.go",
|
||||||
|
"match.go",
|
||||||
|
"minmax.go",
|
||||||
|
"multipleof.go",
|
||||||
|
"not_in.go",
|
||||||
|
"not_nil.go",
|
||||||
|
"required.go",
|
||||||
|
"string.go",
|
||||||
|
"struct.go",
|
||||||
|
"util.go",
|
||||||
|
"validation.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/github.com/go-ozzo/ozzo-validation",
|
||||||
|
importpath = "github.com/go-ozzo/ozzo-validation",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//vendor/github.com/go-ozzo/ozzo-validation/is:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,17 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
Copyright (c) 2016, Qiang Xue
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,534 @@
|
||||||
|
# ozzo-validation
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/go-ozzo/ozzo-validation?status.png)](http://godoc.org/github.com/go-ozzo/ozzo-validation)
|
||||||
|
[![Build Status](https://travis-ci.org/go-ozzo/ozzo-validation.svg?branch=master)](https://travis-ci.org/go-ozzo/ozzo-validation)
|
||||||
|
[![Coverage Status](https://coveralls.io/repos/github/go-ozzo/ozzo-validation/badge.svg?branch=master)](https://coveralls.io/github/go-ozzo/ozzo-validation?branch=master)
|
||||||
|
[![Go Report](https://goreportcard.com/badge/github.com/go-ozzo/ozzo-validation)](https://goreportcard.com/report/github.com/go-ozzo/ozzo-validation)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
ozzo-validation is a Go package that provides configurable and extensible data validation capabilities.
|
||||||
|
It has the following features:
|
||||||
|
|
||||||
|
* use normal programming constructs rather than error-prone struct tags to specify how data should be validated.
|
||||||
|
* can validate data of different types, e.g., structs, strings, byte slices, slices, maps, arrays.
|
||||||
|
* can validate custom data types as long as they implement the `Validatable` interface.
|
||||||
|
* can validate data types that implement the `sql.Valuer` interface (e.g. `sql.NullString`).
|
||||||
|
* customizable and well-formatted validation errors.
|
||||||
|
* provide a rich set of validation rules right out of box.
|
||||||
|
* extremely easy to create and use custom validation rules.
|
||||||
|
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Go 1.8 or above.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
The ozzo-validation package mainly includes a set of validation rules and two validation methods. You use
|
||||||
|
validation rules to describe how a value should be considered valid, and you call either `validation.Validate()`
|
||||||
|
or `validation.ValidateStruct()` to validate the value.
|
||||||
|
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Run the following command to install the package:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/go-ozzo/ozzo-validation
|
||||||
|
go get github.com/go-ozzo/ozzo-validation/is
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validating a Simple Value
|
||||||
|
|
||||||
|
For a simple value, such as a string or an integer, you may use `validation.Validate()` to validate it. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-ozzo/ozzo-validation"
|
||||||
|
"github.com/go-ozzo/ozzo-validation/is"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
data := "example"
|
||||||
|
err := validation.Validate(data,
|
||||||
|
validation.Required, // not empty
|
||||||
|
validation.Length(5, 100), // length between 5 and 100
|
||||||
|
is.URL, // is a valid URL
|
||||||
|
)
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// must be a valid URL
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The method `validation.Validate()` will run through the rules in the order that they are listed. If a rule fails
|
||||||
|
the validation, the method will return the corresponding error and skip the rest of the rules. The method will
|
||||||
|
return nil if the value passes all validation rules.
|
||||||
|
|
||||||
|
|
||||||
|
### Validating a Struct
|
||||||
|
|
||||||
|
For a struct value, you usually want to check if its fields are valid. For example, in a RESTful application, you
|
||||||
|
may unmarshal the request payload into a struct and then validate the struct fields. If one or multiple fields
|
||||||
|
are invalid, you may want to get an error describing which fields are invalid. You can use `validation.ValidateStruct()`
|
||||||
|
to achieve this purpose. A single struct can have rules for multiple fields, and a field can be associated with multiple
|
||||||
|
rules. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/go-ozzo/ozzo-validation"
|
||||||
|
"github.com/go-ozzo/ozzo-validation/is"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
Street string
|
||||||
|
City string
|
||||||
|
State string
|
||||||
|
Zip string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Address) Validate() error {
|
||||||
|
return validation.ValidateStruct(&a,
|
||||||
|
// Street cannot be empty, and the length must between 5 and 50
|
||||||
|
validation.Field(&a.Street, validation.Required, validation.Length(5, 50)),
|
||||||
|
// City cannot be empty, and the length must between 5 and 50
|
||||||
|
validation.Field(&a.City, validation.Required, validation.Length(5, 50)),
|
||||||
|
// State cannot be empty, and must be a string consisting of two letters in upper case
|
||||||
|
validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
|
||||||
|
// State cannot be empty, and must be a string consisting of five digits
|
||||||
|
validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
a := Address{
|
||||||
|
Street: "123",
|
||||||
|
City: "Unknown",
|
||||||
|
State: "Virginia",
|
||||||
|
Zip: "12345",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.Validate()
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// Street: the length must be between 5 and 50; State: must be in a valid format.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that when calling `validation.ValidateStruct` to validate a struct, you should pass to the method a pointer
|
||||||
|
to the struct instead of the struct itself. Similarly, when calling `validation.Field` to specify the rules
|
||||||
|
for a struct field, you should use a pointer to the struct field.
|
||||||
|
|
||||||
|
When the struct validation is performed, the fields are validated in the order they are specified in `ValidateStruct`.
|
||||||
|
And when each field is validated, its rules are also evaluated in the order they are associated with the field.
|
||||||
|
If a rule fails, an error is recorded for that field, and the validation will continue with the next field.
|
||||||
|
|
||||||
|
|
||||||
|
### Validation Errors
|
||||||
|
|
||||||
|
The `validation.ValidateStruct` method returns validation errors found in struct fields in terms of `validation.Errors`
|
||||||
|
which is a map of fields and their corresponding errors. Nil is returned if validation passes.
|
||||||
|
|
||||||
|
By default, `validation.Errors` uses the struct tags named `json` to determine what names should be used to
|
||||||
|
represent the invalid fields. The type also implements the `json.Marshaler` interface so that it can be marshaled
|
||||||
|
into a proper JSON object. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Address struct {
|
||||||
|
Street string `json:"street"`
|
||||||
|
City string `json:"city"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Zip string `json:"zip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...perform validation here...
|
||||||
|
|
||||||
|
err := a.Validate()
|
||||||
|
b, _ := json.Marshal(err)
|
||||||
|
fmt.Println(string(b))
|
||||||
|
// Output:
|
||||||
|
// {"street":"the length must be between 5 and 50","state":"must be in a valid format"}
|
||||||
|
```
|
||||||
|
|
||||||
|
You may modify `validation.ErrorTag` to use a different struct tag name.
|
||||||
|
|
||||||
|
If you do not like the magic that `ValidateStruct` determines error keys based on struct field names or corresponding
|
||||||
|
tag values, you may use the following alternative approach:
|
||||||
|
|
||||||
|
```go
|
||||||
|
c := Customer{
|
||||||
|
Name: "Qiang Xue",
|
||||||
|
Email: "q",
|
||||||
|
Address: Address{
|
||||||
|
State: "Virginia",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validation.Errors{
|
||||||
|
"name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)),
|
||||||
|
"email": validation.Validate(c.Name, validation.Required, is.Email),
|
||||||
|
"zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
|
||||||
|
}.Filter()
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// email: must be a valid email address; zip: cannot be blank.
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, we build a `validation.Errors` by a list of names and the corresponding validation results.
|
||||||
|
At the end we call `Errors.Filter()` to remove from `Errors` all nils which correspond to those successful validation
|
||||||
|
results. The method will return nil if `Errors` is empty.
|
||||||
|
|
||||||
|
The above approach is very flexible as it allows you to freely build up your validation error structure. You can use
|
||||||
|
it to validate both struct and non-struct values. Compared to using `ValidateStruct` to validate a struct,
|
||||||
|
it has the drawback that you have to redundantly specify the error keys while `ValidateStruct` can automatically
|
||||||
|
find them out.
|
||||||
|
|
||||||
|
|
||||||
|
### Internal Errors
|
||||||
|
|
||||||
|
Internal errors are different from validation errors in that internal errors are caused by malfunctioning code (e.g.
|
||||||
|
a validator making a remote call to validate some data when the remote service is down) rather
|
||||||
|
than the data being validated. When an internal error happens during data validation, you may allow the user to resubmit
|
||||||
|
the same data to perform validation again, hoping the program resumes functioning. On the other hand, if data validation
|
||||||
|
fails due to data error, the user should generally not resubmit the same data again.
|
||||||
|
|
||||||
|
To differentiate internal errors from validation errors, when an internal error occurs in a validator, wrap it
|
||||||
|
into `validation.InternalError` by calling `validation.NewInternalError()`. The user of the validator can then check
|
||||||
|
if a returned error is an internal error or not. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := a.Validate(); err != nil {
|
||||||
|
if e, ok := err.(validation.InternalError); ok {
|
||||||
|
// an internal error happened
|
||||||
|
fmt.Println(e.InternalError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Validatable Types
|
||||||
|
|
||||||
|
A type is validatable if it implements the `validation.Validatable` interface.
|
||||||
|
|
||||||
|
When `validation.Validate` is used to validate a validatable value, if it does not find any error with the
|
||||||
|
given validation rules, it will further call the value's `Validate()` method.
|
||||||
|
|
||||||
|
Similarly, when `validation.ValidateStruct` is validating a struct field whose type is validatable, it will call
|
||||||
|
the field's `Validate` method after it passes the listed rules.
|
||||||
|
|
||||||
|
In the following example, the `Address` field of `Customer` is validatable because `Address` implements
|
||||||
|
`validation.Validatable`. Therefore, when validating a `Customer` struct with `validation.ValidateStruct`,
|
||||||
|
validation will "dive" into the `Address` field.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Customer struct {
|
||||||
|
Name string
|
||||||
|
Gender string
|
||||||
|
Email string
|
||||||
|
Address Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Customer) Validate() error {
|
||||||
|
return validation.ValidateStruct(&c,
|
||||||
|
// Name cannot be empty, and the length must be between 5 and 20.
|
||||||
|
validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
|
||||||
|
// Gender is optional, and should be either "Female" or "Male".
|
||||||
|
validation.Field(&c.Gender, validation.In("Female", "Male")),
|
||||||
|
// Email cannot be empty and should be in a valid email format.
|
||||||
|
validation.Field(&c.Email, validation.Required, is.Email),
|
||||||
|
// Validate Address using its own validation rules
|
||||||
|
validation.Field(&c.Address),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := Customer{
|
||||||
|
Name: "Qiang Xue",
|
||||||
|
Email: "q",
|
||||||
|
Address: Address{
|
||||||
|
Street: "123 Main Street",
|
||||||
|
City: "Unknown",
|
||||||
|
State: "Virginia",
|
||||||
|
Zip: "12345",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Validate()
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// Address: (State: must be in a valid format.); Email: must be a valid email address.
|
||||||
|
```
|
||||||
|
|
||||||
|
Sometimes, you may want to skip the invocation of a type's `Validate` method. To do so, simply associate
|
||||||
|
a `validation.Skip` rule with the value being validated.
|
||||||
|
|
||||||
|
|
||||||
|
### Maps/Slices/Arrays of Validatables
|
||||||
|
|
||||||
|
When validating a map, slice, or array, whose element type implements the `validation.Validatable` interface,
|
||||||
|
the `validation.Validate` method will call the `Validate` method of every non-nil element.
|
||||||
|
The validation errors of the elements will be returned as `validation.Errors` which maps the keys of the
|
||||||
|
invalid elements to their corresponding validation errors. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
addresses := []Address{
|
||||||
|
Address{State: "MD", Zip: "12345"},
|
||||||
|
Address{Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"},
|
||||||
|
Address{City: "Unknown", State: "NC", Zip: "123"},
|
||||||
|
}
|
||||||
|
err := validation.Validate(addresses)
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.).
|
||||||
|
```
|
||||||
|
|
||||||
|
When using `validation.ValidateStruct` to validate a struct, the above validation procedure also applies to those struct
|
||||||
|
fields which are map/slices/arrays of validatables.
|
||||||
|
|
||||||
|
|
||||||
|
### Pointers
|
||||||
|
|
||||||
|
When a value being validated is a pointer, most validation rules will validate the actual value pointed to by the pointer.
|
||||||
|
If the pointer is nil, these rules will skip the validation.
|
||||||
|
|
||||||
|
An exception is the `validation.Required` and `validation.NotNil` rules. When a pointer is nil, they
|
||||||
|
will report a validation error.
|
||||||
|
|
||||||
|
|
||||||
|
### Types Implementing `sql.Valuer`
|
||||||
|
|
||||||
|
If a data type implements the `sql.Valuer` interface (e.g. `sql.NullString`), the built-in validation rules will handle
|
||||||
|
it properly. In particular, when a rule is validating such data, it will call the `Value()` method and validate
|
||||||
|
the returned value instead.
|
||||||
|
|
||||||
|
|
||||||
|
### Required vs. Not Nil
|
||||||
|
|
||||||
|
When validating input values, there are two different scenarios about checking if input values are provided or not.
|
||||||
|
|
||||||
|
In the first scenario, an input value is considered missing if it is not entered or it is entered as a zero value
|
||||||
|
(e.g. an empty string, a zero integer). You can use the `validation.Required` rule in this case.
|
||||||
|
|
||||||
|
In the second scenario, an input value is considered missing only if it is not entered. A pointer field is usually
|
||||||
|
used in this case so that you can detect if a value is entered or not by checking if the pointer is nil or not.
|
||||||
|
You can use the `validation.NotNil` rule to ensure a value is entered (even if it is a zero value).
|
||||||
|
|
||||||
|
|
||||||
|
### Embedded Structs
|
||||||
|
|
||||||
|
The `validation.ValidateStruct` method will properly validate a struct that contains embedded structs. In particular,
|
||||||
|
the fields of an embedded struct are treated as if they belong directly to the containing struct. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Employee struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ()
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
Employee
|
||||||
|
Level int
|
||||||
|
}
|
||||||
|
|
||||||
|
m := Manager{}
|
||||||
|
err := validation.ValidateStruct(&m,
|
||||||
|
validation.Field(&m.Name, validation.Required),
|
||||||
|
validation.Field(&m.Level, validation.Required),
|
||||||
|
)
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// Level: cannot be blank; Name: cannot be blank.
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above code, we use `&m.Name` to specify the validation of the `Name` field of the embedded struct `Employee`.
|
||||||
|
And the validation error uses `Name` as the key for the error associated with the `Name` field as if `Name` a field
|
||||||
|
directly belonging to `Manager`.
|
||||||
|
|
||||||
|
If `Employee` implements the `validation.Validatable` interface, we can also use the following code to validate
|
||||||
|
`Manager`, which generates the same validation result:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (e Employee) Validate() error {
|
||||||
|
return validation.ValidateStruct(&e,
|
||||||
|
validation.Field(&e.Name, validation.Required),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validation.ValidateStruct(&m,
|
||||||
|
validation.Field(&m.Employee),
|
||||||
|
validation.Field(&m.Level, validation.Required),
|
||||||
|
)
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// Level: cannot be blank; Name: cannot be blank.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Built-in Validation Rules
|
||||||
|
|
||||||
|
The following rules are provided in the `validation` package:
|
||||||
|
|
||||||
|
* `In(...interface{})`: checks if a value can be found in the given list of values.
|
||||||
|
* `Length(min, max int)`: checks if the length of a value is within the specified range.
|
||||||
|
This rule should only be used for validating strings, slices, maps, and arrays.
|
||||||
|
* `RuneLength(min, max int)`: checks if the length of a string is within the specified range.
|
||||||
|
This rule is similar as `Length` except that when the value being validated is a string, it checks
|
||||||
|
its rune length instead of byte length.
|
||||||
|
* `Min(min interface{})` and `Max(max interface{})`: checks if a value is within the specified range.
|
||||||
|
These two rules should only be used for validating int, uint, float and time.Time types.
|
||||||
|
* `Match(*regexp.Regexp)`: checks if a value matches the specified regular expression.
|
||||||
|
This rule should only be used for strings and byte slices.
|
||||||
|
* `Date(layout string)`: checks if a string value is a date whose format is specified by the layout.
|
||||||
|
By calling `Min()` and/or `Max()`, you can check additionally if the date is within the specified range.
|
||||||
|
* `Required`: checks if a value is not empty (neither nil nor zero).
|
||||||
|
* `NotNil`: checks if a pointer value is not nil. Non-pointer values are considered valid.
|
||||||
|
* `NilOrNotEmpty`: checks if a value is a nil pointer or a non-empty value. This differs from `Required` in that it treats a nil pointer as valid.
|
||||||
|
* `Skip`: this is a special rule used to indicate that all rules following it should be skipped (including the nested ones).
|
||||||
|
* `MultipleOf`: checks if the value is a multiple of the specified range.
|
||||||
|
|
||||||
|
The `is` sub-package provides a list of commonly used string validation rules that can be used to check if the format
|
||||||
|
of a value satisfies certain requirements. Note that these rules only handle strings and byte slices and if a string
|
||||||
|
or byte slice is empty, it is considered valid. You may use a `Required` rule to ensure a value is not empty.
|
||||||
|
Below is the whole list of the rules provided by the `is` package:
|
||||||
|
|
||||||
|
* `Email`: validates if a string is an email or not
|
||||||
|
* `URL`: validates if a string is a valid URL
|
||||||
|
* `RequestURL`: validates if a string is a valid request URL
|
||||||
|
* `RequestURI`: validates if a string is a valid request URI
|
||||||
|
* `Alpha`: validates if a string contains English letters only (a-zA-Z)
|
||||||
|
* `Digit`: validates if a string contains digits only (0-9)
|
||||||
|
* `Alphanumeric`: validates if a string contains English letters and digits only (a-zA-Z0-9)
|
||||||
|
* `UTFLetter`: validates if a string contains unicode letters only
|
||||||
|
* `UTFDigit`: validates if a string contains unicode decimal digits only
|
||||||
|
* `UTFLetterNumeric`: validates if a string contains unicode letters and numbers only
|
||||||
|
* `UTFNumeric`: validates if a string contains unicode number characters (category N) only
|
||||||
|
* `LowerCase`: validates if a string contains lower case unicode letters only
|
||||||
|
* `UpperCase`: validates if a string contains upper case unicode letters only
|
||||||
|
* `Hexadecimal`: validates if a string is a valid hexadecimal number
|
||||||
|
* `HexColor`: validates if a string is a valid hexadecimal color code
|
||||||
|
* `RGBColor`: validates if a string is a valid RGB color in the form of rgb(R, G, B)
|
||||||
|
* `Int`: validates if a string is a valid integer number
|
||||||
|
* `Float`: validates if a string is a floating point number
|
||||||
|
* `UUIDv3`: validates if a string is a valid version 3 UUID
|
||||||
|
* `UUIDv4`: validates if a string is a valid version 4 UUID
|
||||||
|
* `UUIDv5`: validates if a string is a valid version 5 UUID
|
||||||
|
* `UUID`: validates if a string is a valid UUID
|
||||||
|
* `CreditCard`: validates if a string is a valid credit card number
|
||||||
|
* `ISBN10`: validates if a string is an ISBN version 10
|
||||||
|
* `ISBN13`: validates if a string is an ISBN version 13
|
||||||
|
* `ISBN`: validates if a string is an ISBN (either version 10 or 13)
|
||||||
|
* `JSON`: validates if a string is in valid JSON format
|
||||||
|
* `ASCII`: validates if a string contains ASCII characters only
|
||||||
|
* `PrintableASCII`: validates if a string contains printable ASCII characters only
|
||||||
|
* `Multibyte`: validates if a string contains multibyte characters
|
||||||
|
* `FullWidth`: validates if a string contains full-width characters
|
||||||
|
* `HalfWidth`: validates if a string contains half-width characters
|
||||||
|
* `VariableWidth`: validates if a string contains both full-width and half-width characters
|
||||||
|
* `Base64`: validates if a string is encoded in Base64
|
||||||
|
* `DataURI`: validates if a string is a valid base64-encoded data URI
|
||||||
|
* `E164`: validates if a string is a valid E164 phone number (+19251232233)
|
||||||
|
* `CountryCode2`: validates if a string is a valid ISO3166 Alpha 2 country code
|
||||||
|
* `CountryCode3`: validates if a string is a valid ISO3166 Alpha 3 country code
|
||||||
|
* `DialString`: validates if a string is a valid dial string that can be passed to Dial()
|
||||||
|
* `MAC`: validates if a string is a MAC address
|
||||||
|
* `IP`: validates if a string is a valid IP address (either version 4 or 6)
|
||||||
|
* `IPv4`: validates if a string is a valid version 4 IP address
|
||||||
|
* `IPv6`: validates if a string is a valid version 6 IP address
|
||||||
|
* `Subdomain`: validates if a string is valid subdomain
|
||||||
|
* `Domain`: validates if a string is valid domain
|
||||||
|
* `DNSName`: validates if a string is valid DNS name
|
||||||
|
* `Host`: validates if a string is a valid IP (both v4 and v6) or a valid DNS name
|
||||||
|
* `Port`: validates if a string is a valid port number
|
||||||
|
* `MongoID`: validates if a string is a valid Mongo ID
|
||||||
|
* `Latitude`: validates if a string is a valid latitude
|
||||||
|
* `Longitude`: validates if a string is a valid longitude
|
||||||
|
* `SSN`: validates if a string is a social security number (SSN)
|
||||||
|
* `Semver`: validates if a string is a valid semantic version
|
||||||
|
|
||||||
|
### Customizing Error Messages
|
||||||
|
|
||||||
|
All built-in validation rules allow you to customize error messages. To do so, simply call the `Error()` method
|
||||||
|
of the rules. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
data := "2123"
|
||||||
|
err := validation.Validate(data,
|
||||||
|
validation.Required.Error("is required"),
|
||||||
|
validation.Match(regexp.MustCompile("^[0-9]{5}$")).Error("must be a string with five digits"),
|
||||||
|
)
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// must be a string with five digits
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Creating Custom Rules
|
||||||
|
|
||||||
|
Creating a custom rule is as simple as implementing the `validation.Rule` interface. The interface contains a single
|
||||||
|
method as shown below, which should validate the value and return the validation error, if any:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Validate validates a value and returns an error if validation fails.
|
||||||
|
Validate(value interface{}) error
|
||||||
|
```
|
||||||
|
|
||||||
|
If you already have a function with the same signature as shown above, you can call `validation.By()` to turn
|
||||||
|
it into a validation rule. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
func checkAbc(value interface{}) error {
|
||||||
|
s, _ := value.(string)
|
||||||
|
if s != "abc" {
|
||||||
|
return errors.New("must be abc")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validation.Validate("xyz", validation.By(checkAbc))
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output: must be abc
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Rule Groups
|
||||||
|
|
||||||
|
When a combination of several rules are used in multiple places, you may use the following trick to create a
|
||||||
|
rule group so that your code is more maintainable.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var NameRule = []validation.Rule{
|
||||||
|
validation.Required,
|
||||||
|
validation.Length(5, 20),
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u User) Validate() error {
|
||||||
|
return validation.ValidateStruct(&u,
|
||||||
|
validation.Field(&u.FirstName, NameRule...),
|
||||||
|
validation.Field(&u.LastName, NameRule...),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, we create a rule group `NameRule` which consists of two validation rules. We then use this rule
|
||||||
|
group to validate both `FirstName` and `LastName`.
|
||||||
|
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
The `is` sub-package wraps the excellent validators provided by the [govalidator](https://github.com/asaskevich/govalidator) package.
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Upgrade Instructions
|
||||||
|
|
||||||
|
## Upgrade from 2.x to 3.x
|
||||||
|
|
||||||
|
* Instead of using `StructRules` to define struct validation rules, use `ValidateStruct()` to declare and perform
|
||||||
|
struct validation. The following code snippet shows how to modify your code:
|
||||||
|
```go
|
||||||
|
// 2.x usage
|
||||||
|
err := validation.StructRules{}.
|
||||||
|
Add("Street", validation.Required, validation.Length(5, 50)).
|
||||||
|
Add("City", validation.Required, validation.Length(5, 50)).
|
||||||
|
Add("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))).
|
||||||
|
Add("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))).
|
||||||
|
Validate(a)
|
||||||
|
|
||||||
|
// 3.x usage
|
||||||
|
err := validation.ValidateStruct(&a,
|
||||||
|
validation.Field(&a.Street, validation.Required, validation.Length(5, 50)),
|
||||||
|
validation.Field(&a.City, validation.Required, validation.Length(5, 50)),
|
||||||
|
validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
|
||||||
|
validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
* Instead of using `Rules` to declare a rule list and use it to validate a value, call `Validate()` with the rules directly.
|
||||||
|
```go
|
||||||
|
data := "example"
|
||||||
|
|
||||||
|
// 2.x usage
|
||||||
|
rules := validation.Rules{
|
||||||
|
validation.Required,
|
||||||
|
validation.Length(5, 100),
|
||||||
|
is.URL,
|
||||||
|
}
|
||||||
|
err := rules.Validate(data)
|
||||||
|
|
||||||
|
// 3.x usage
|
||||||
|
err := validation.Validate(data,
|
||||||
|
validation.Required,
|
||||||
|
validation.Length(5, 100),
|
||||||
|
is.URL,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
* The default struct tags used for determining error keys is changed from `validation` to `json`. You may modify
|
||||||
|
`validation.ErrorTag` to change it back.
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DateRule struct {
|
||||||
|
layout string
|
||||||
|
min, max time.Time
|
||||||
|
message string
|
||||||
|
rangeMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date returns a validation rule that checks if a string value is in a format that can be parsed into a date.
|
||||||
|
// The format of the date should be specified as the layout parameter which accepts the same value as that for time.Parse.
|
||||||
|
// For example,
|
||||||
|
// validation.Date(time.ANSIC)
|
||||||
|
// validation.Date("02 Jan 06 15:04 MST")
|
||||||
|
// validation.Date("2006-01-02")
|
||||||
|
//
|
||||||
|
// By calling Min() and/or Max(), you can let the Date rule to check if a parsed date value is within
|
||||||
|
// the specified date range.
|
||||||
|
//
|
||||||
|
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
|
||||||
|
func Date(layout string) *DateRule {
|
||||||
|
return &DateRule{
|
||||||
|
layout: layout,
|
||||||
|
message: "must be a valid date",
|
||||||
|
rangeMessage: "the data is out of range",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message that is used when the value being validated is not a valid date.
|
||||||
|
func (r *DateRule) Error(message string) *DateRule {
|
||||||
|
r.message = message
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeError sets the error message that is used when the value being validated is out of the specified Min/Max date range.
|
||||||
|
func (r *DateRule) RangeError(message string) *DateRule {
|
||||||
|
r.rangeMessage = message
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Min sets the minimum date range. A zero value means skipping the minimum range validation.
|
||||||
|
func (r *DateRule) Min(min time.Time) *DateRule {
|
||||||
|
r.min = min
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max sets the maximum date range. A zero value means skipping the maximum range validation.
|
||||||
|
func (r *DateRule) Max(max time.Time) *DateRule {
|
||||||
|
r.max = max
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is a valid date.
|
||||||
|
func (r *DateRule) Validate(value interface{}) error {
|
||||||
|
value, isNil := Indirect(value)
|
||||||
|
if isNil || IsEmpty(value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, err := EnsureString(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
date, err := time.Parse(r.layout, str)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(r.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.min.IsZero() && r.min.After(date) || !r.max.IsZero() && date.After(r.max) {
|
||||||
|
return errors.New(r.rangeMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Errors represents the validation errors that are indexed by struct field names, map or slice keys.
|
||||||
|
Errors map[string]error
|
||||||
|
|
||||||
|
// InternalError represents an error that should NOT be treated as a validation error.
|
||||||
|
InternalError interface {
|
||||||
|
error
|
||||||
|
InternalError() error
|
||||||
|
}
|
||||||
|
|
||||||
|
internalError struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewInternalError wraps a given error into an InternalError.
|
||||||
|
func NewInternalError(err error) InternalError {
|
||||||
|
return &internalError{error: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalError returns the actual error that it wraps around.
|
||||||
|
func (e *internalError) InternalError() error {
|
||||||
|
return e.error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the error string of Errors.
|
||||||
|
func (es Errors) Error() string {
|
||||||
|
if len(es) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := []string{}
|
||||||
|
for key := range es {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
s := ""
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
s += "; "
|
||||||
|
}
|
||||||
|
if errs, ok := es[key].(Errors); ok {
|
||||||
|
s += fmt.Sprintf("%v: (%v)", key, errs)
|
||||||
|
} else {
|
||||||
|
s += fmt.Sprintf("%v: %v", key, es[key].Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts the Errors into a valid JSON.
|
||||||
|
func (es Errors) MarshalJSON() ([]byte, error) {
|
||||||
|
errs := map[string]interface{}{}
|
||||||
|
for key, err := range es {
|
||||||
|
if ms, ok := err.(json.Marshaler); ok {
|
||||||
|
errs[key] = ms
|
||||||
|
} else {
|
||||||
|
errs[key] = err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.Marshal(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter removes all nils from Errors and returns back the updated Errors as an error.
|
||||||
|
// If the length of Errors becomes 0, it will return nil.
|
||||||
|
func (es Errors) Filter() error {
|
||||||
|
for key, value := range es {
|
||||||
|
if value == nil {
|
||||||
|
delete(es, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(es) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return es
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// In returns a validation rule that checks if a value can be found in the given list of values.
|
||||||
|
// Note that the value being checked and the possible range of values must be of the same type.
|
||||||
|
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
|
||||||
|
func In(values ...interface{}) *InRule {
|
||||||
|
return &InRule{
|
||||||
|
elements: values,
|
||||||
|
message: "must be a valid value",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type InRule struct {
|
||||||
|
elements []interface{}
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is valid or not.
|
||||||
|
func (r *InRule) Validate(value interface{}) error {
|
||||||
|
value, isNil := Indirect(value)
|
||||||
|
if isNil || IsEmpty(value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range r.elements {
|
||||||
|
if e == value {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New(r.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (r *InRule) Error(message string) *InRule {
|
||||||
|
r.message = message
|
||||||
|
return r
|
||||||
|
}
|
|
@ -2,10 +2,14 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["godbc.go"],
|
srcs = ["rules.go"],
|
||||||
importmap = "k8s.io/kubernetes/vendor/github.com/lpabon/godbc",
|
importmap = "k8s.io/kubernetes/vendor/github.com/go-ozzo/ozzo-validation/is",
|
||||||
importpath = "github.com/lpabon/godbc",
|
importpath = "github.com/go-ozzo/ozzo-validation/is",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/asaskevich/govalidator:go_default_library",
|
||||||
|
"//vendor/github.com/go-ozzo/ozzo-validation:go_default_library",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package is provides a list of commonly used string validation rules.
|
||||||
|
package is
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/asaskevich/govalidator"
|
||||||
|
"github.com/go-ozzo/ozzo-validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Email validates if a string is an email or not.
|
||||||
|
Email = validation.NewStringRule(govalidator.IsEmail, "must be a valid email address")
|
||||||
|
// URL validates if a string is a valid URL
|
||||||
|
URL = validation.NewStringRule(govalidator.IsURL, "must be a valid URL")
|
||||||
|
// RequestURL validates if a string is a valid request URL
|
||||||
|
RequestURL = validation.NewStringRule(govalidator.IsRequestURL, "must be a valid request URL")
|
||||||
|
// RequestURI validates if a string is a valid request URI
|
||||||
|
RequestURI = validation.NewStringRule(govalidator.IsRequestURI, "must be a valid request URI")
|
||||||
|
// Alpha validates if a string contains English letters only (a-zA-Z)
|
||||||
|
Alpha = validation.NewStringRule(govalidator.IsAlpha, "must contain English letters only")
|
||||||
|
// Digit validates if a string contains digits only (0-9)
|
||||||
|
Digit = validation.NewStringRule(isDigit, "must contain digits only")
|
||||||
|
// Alphanumeric validates if a string contains English letters and digits only (a-zA-Z0-9)
|
||||||
|
Alphanumeric = validation.NewStringRule(govalidator.IsAlphanumeric, "must contain English letters and digits only")
|
||||||
|
// UTFLetter validates if a string contains unicode letters only
|
||||||
|
UTFLetter = validation.NewStringRule(govalidator.IsUTFLetter, "must contain unicode letter characters only")
|
||||||
|
// UTFDigit validates if a string contains unicode decimal digits only
|
||||||
|
UTFDigit = validation.NewStringRule(govalidator.IsUTFDigit, "must contain unicode decimal digits only")
|
||||||
|
// UTFLetterNumeric validates if a string contains unicode letters and numbers only
|
||||||
|
UTFLetterNumeric = validation.NewStringRule(govalidator.IsUTFLetterNumeric, "must contain unicode letters and numbers only")
|
||||||
|
// UTFNumeric validates if a string contains unicode number characters (category N) only
|
||||||
|
UTFNumeric = validation.NewStringRule(isUTFNumeric, "must contain unicode number characters only")
|
||||||
|
// LowerCase validates if a string contains lower case unicode letters only
|
||||||
|
LowerCase = validation.NewStringRule(govalidator.IsLowerCase, "must be in lower case")
|
||||||
|
// UpperCase validates if a string contains upper case unicode letters only
|
||||||
|
UpperCase = validation.NewStringRule(govalidator.IsUpperCase, "must be in upper case")
|
||||||
|
// Hexadecimal validates if a string is a valid hexadecimal number
|
||||||
|
Hexadecimal = validation.NewStringRule(govalidator.IsHexadecimal, "must be a valid hexadecimal number")
|
||||||
|
// HexColor validates if a string is a valid hexadecimal color code
|
||||||
|
HexColor = validation.NewStringRule(govalidator.IsHexcolor, "must be a valid hexadecimal color code")
|
||||||
|
// RGBColor validates if a string is a valid RGB color in the form of rgb(R, G, B)
|
||||||
|
RGBColor = validation.NewStringRule(govalidator.IsRGBcolor, "must be a valid RGB color code")
|
||||||
|
// Int validates if a string is a valid integer number
|
||||||
|
Int = validation.NewStringRule(govalidator.IsInt, "must be an integer number")
|
||||||
|
// Float validates if a string is a floating point number
|
||||||
|
Float = validation.NewStringRule(govalidator.IsFloat, "must be a floating point number")
|
||||||
|
// UUIDv3 validates if a string is a valid version 3 UUID
|
||||||
|
UUIDv3 = validation.NewStringRule(govalidator.IsUUIDv3, "must be a valid UUID v3")
|
||||||
|
// UUIDv4 validates if a string is a valid version 4 UUID
|
||||||
|
UUIDv4 = validation.NewStringRule(govalidator.IsUUIDv4, "must be a valid UUID v4")
|
||||||
|
// UUIDv5 validates if a string is a valid version 5 UUID
|
||||||
|
UUIDv5 = validation.NewStringRule(govalidator.IsUUIDv5, "must be a valid UUID v5")
|
||||||
|
// UUID validates if a string is a valid UUID
|
||||||
|
UUID = validation.NewStringRule(govalidator.IsUUID, "must be a valid UUID")
|
||||||
|
// CreditCard validates if a string is a valid credit card number
|
||||||
|
CreditCard = validation.NewStringRule(govalidator.IsCreditCard, "must be a valid credit card number")
|
||||||
|
// ISBN10 validates if a string is an ISBN version 10
|
||||||
|
ISBN10 = validation.NewStringRule(govalidator.IsISBN10, "must be a valid ISBN-10")
|
||||||
|
// ISBN13 validates if a string is an ISBN version 13
|
||||||
|
ISBN13 = validation.NewStringRule(govalidator.IsISBN13, "must be a valid ISBN-13")
|
||||||
|
// ISBN validates if a string is an ISBN (either version 10 or 13)
|
||||||
|
ISBN = validation.NewStringRule(isISBN, "must be a valid ISBN")
|
||||||
|
// JSON validates if a string is in valid JSON format
|
||||||
|
JSON = validation.NewStringRule(govalidator.IsJSON, "must be in valid JSON format")
|
||||||
|
// ASCII validates if a string contains ASCII characters only
|
||||||
|
ASCII = validation.NewStringRule(govalidator.IsASCII, "must contain ASCII characters only")
|
||||||
|
// PrintableASCII validates if a string contains printable ASCII characters only
|
||||||
|
PrintableASCII = validation.NewStringRule(govalidator.IsPrintableASCII, "must contain printable ASCII characters only")
|
||||||
|
// Multibyte validates if a string contains multibyte characters
|
||||||
|
Multibyte = validation.NewStringRule(govalidator.IsMultibyte, "must contain multibyte characters")
|
||||||
|
// FullWidth validates if a string contains full-width characters
|
||||||
|
FullWidth = validation.NewStringRule(govalidator.IsFullWidth, "must contain full-width characters")
|
||||||
|
// HalfWidth validates if a string contains half-width characters
|
||||||
|
HalfWidth = validation.NewStringRule(govalidator.IsHalfWidth, "must contain half-width characters")
|
||||||
|
// VariableWidth validates if a string contains both full-width and half-width characters
|
||||||
|
VariableWidth = validation.NewStringRule(govalidator.IsVariableWidth, "must contain both full-width and half-width characters")
|
||||||
|
// Base64 validates if a string is encoded in Base64
|
||||||
|
Base64 = validation.NewStringRule(govalidator.IsBase64, "must be encoded in Base64")
|
||||||
|
// DataURI validates if a string is a valid base64-encoded data URI
|
||||||
|
DataURI = validation.NewStringRule(govalidator.IsDataURI, "must be a Base64-encoded data URI")
|
||||||
|
// E164 validates if a string is a valid ISO3166 Alpha 2 country code
|
||||||
|
E164 = validation.NewStringRule(isE164Number, "must be a valid E164 number")
|
||||||
|
// CountryCode2 validates if a string is a valid ISO3166 Alpha 2 country code
|
||||||
|
CountryCode2 = validation.NewStringRule(govalidator.IsISO3166Alpha2, "must be a valid two-letter country code")
|
||||||
|
// CountryCode3 validates if a string is a valid ISO3166 Alpha 3 country code
|
||||||
|
CountryCode3 = validation.NewStringRule(govalidator.IsISO3166Alpha3, "must be a valid three-letter country code")
|
||||||
|
// DialString validates if a string is a valid dial string that can be passed to Dial()
|
||||||
|
DialString = validation.NewStringRule(govalidator.IsDialString, "must be a valid dial string")
|
||||||
|
// MAC validates if a string is a MAC address
|
||||||
|
MAC = validation.NewStringRule(govalidator.IsMAC, "must be a valid MAC address")
|
||||||
|
// IP validates if a string is a valid IP address (either version 4 or 6)
|
||||||
|
IP = validation.NewStringRule(govalidator.IsIP, "must be a valid IP address")
|
||||||
|
// IPv4 validates if a string is a valid version 4 IP address
|
||||||
|
IPv4 = validation.NewStringRule(govalidator.IsIPv4, "must be a valid IPv4 address")
|
||||||
|
// IPv6 validates if a string is a valid version 6 IP address
|
||||||
|
IPv6 = validation.NewStringRule(govalidator.IsIPv6, "must be a valid IPv6 address")
|
||||||
|
// Subdomain validates if a string is valid subdomain
|
||||||
|
Subdomain = validation.NewStringRule(isSubdomain, "must be a valid subdomain")
|
||||||
|
// Domain validates if a string is valid domain
|
||||||
|
Domain = validation.NewStringRule(isDomain, "must be a valid domain")
|
||||||
|
// DNSName validates if a string is valid DNS name
|
||||||
|
DNSName = validation.NewStringRule(govalidator.IsDNSName, "must be a valid DNS name")
|
||||||
|
// Host validates if a string is a valid IP (both v4 and v6) or a valid DNS name
|
||||||
|
Host = validation.NewStringRule(govalidator.IsHost, "must be a valid IP address or DNS name")
|
||||||
|
// Port validates if a string is a valid port number
|
||||||
|
Port = validation.NewStringRule(govalidator.IsPort, "must be a valid port number")
|
||||||
|
// MongoID validates if a string is a valid Mongo ID
|
||||||
|
MongoID = validation.NewStringRule(govalidator.IsMongoID, "must be a valid hex-encoded MongoDB ObjectId")
|
||||||
|
// Latitude validates if a string is a valid latitude
|
||||||
|
Latitude = validation.NewStringRule(govalidator.IsLatitude, "must be a valid latitude")
|
||||||
|
// Longitude validates if a string is a valid longitude
|
||||||
|
Longitude = validation.NewStringRule(govalidator.IsLongitude, "must be a valid longitude")
|
||||||
|
// SSN validates if a string is a social security number (SSN)
|
||||||
|
SSN = validation.NewStringRule(govalidator.IsSSN, "must be a valid social security number")
|
||||||
|
// Semver validates if a string is a valid semantic version
|
||||||
|
Semver = validation.NewStringRule(govalidator.IsSemver, "must be a valid semantic version")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
reDigit = regexp.MustCompile("^[0-9]+$")
|
||||||
|
// Subdomain regex source: https://stackoverflow.com/a/7933253
|
||||||
|
reSubdomain = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func isISBN(value string) bool {
|
||||||
|
return govalidator.IsISBN(value, 10) || govalidator.IsISBN(value, 13)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigit(value string) bool {
|
||||||
|
return reDigit.MatchString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isE164Number(value string) bool {
|
||||||
|
// E164 regex source: https://stackoverflow.com/a/23299989
|
||||||
|
reE164 := regexp.MustCompile(`^\+?[1-9]\d{1,14}$`)
|
||||||
|
return reE164.MatchString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSubdomain(value string) bool {
|
||||||
|
// Subdomain regex source: https://stackoverflow.com/a/7933253
|
||||||
|
reSubdomain := regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$`)
|
||||||
|
return reSubdomain.MatchString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDomain(value string) bool {
|
||||||
|
if len(value) > 255 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain regex source: https://stackoverflow.com/a/7933253
|
||||||
|
// Slightly modified: Removed 255 max length validation since Go regex does not
|
||||||
|
// support lookarounds. More info: https://stackoverflow.com/a/38935027
|
||||||
|
reDomain := regexp.MustCompile(`^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{1,63}| xn--[a-z0-9]{1,59})$`)
|
||||||
|
|
||||||
|
return reDomain.MatchString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUTFNumeric(value string) bool {
|
||||||
|
for _, c := range value {
|
||||||
|
if unicode.IsNumber(c) == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Length returns a validation rule that checks if a value's length is within the specified range.
|
||||||
|
// If max is 0, it means there is no upper bound for the length.
|
||||||
|
// This rule should only be used for validating strings, slices, maps, and arrays.
|
||||||
|
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
|
||||||
|
func Length(min, max int) *LengthRule {
|
||||||
|
message := "the value must be empty"
|
||||||
|
if min == 0 && max > 0 {
|
||||||
|
message = fmt.Sprintf("the length must be no more than %v", max)
|
||||||
|
} else if min > 0 && max == 0 {
|
||||||
|
message = fmt.Sprintf("the length must be no less than %v", min)
|
||||||
|
} else if min > 0 && max > 0 {
|
||||||
|
if min == max {
|
||||||
|
message = fmt.Sprintf("the length must be exactly %v", min)
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf("the length must be between %v and %v", min, max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &LengthRule{
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuneLength returns a validation rule that checks if a string's rune length is within the specified range.
|
||||||
|
// If max is 0, it means there is no upper bound for the length.
|
||||||
|
// This rule should only be used for validating strings, slices, maps, and arrays.
|
||||||
|
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
|
||||||
|
// If the value being validated is not a string, the rule works the same as Length.
|
||||||
|
func RuneLength(min, max int) *LengthRule {
|
||||||
|
r := Length(min, max)
|
||||||
|
r.rune = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type LengthRule struct {
|
||||||
|
min, max int
|
||||||
|
message string
|
||||||
|
rune bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is valid or not.
|
||||||
|
func (v *LengthRule) Validate(value interface{}) error {
|
||||||
|
value, isNil := Indirect(value)
|
||||||
|
if isNil || IsEmpty(value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if s, ok := value.(string); ok && v.rune {
|
||||||
|
l = utf8.RuneCountInString(s)
|
||||||
|
} else if l, err = LengthOfValue(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.min > 0 && l < v.min || v.max > 0 && l > v.max {
|
||||||
|
return errors.New(v.message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (v *LengthRule) Error(message string) *LengthRule {
|
||||||
|
v.message = message
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Match returns a validation rule that checks if a value matches the specified regular expression.
|
||||||
|
// This rule should only be used for validating strings and byte slices, or a validation error will be reported.
|
||||||
|
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
|
||||||
|
func Match(re *regexp.Regexp) *MatchRule {
|
||||||
|
return &MatchRule{
|
||||||
|
re: re,
|
||||||
|
message: "must be in a valid format",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatchRule struct {
|
||||||
|
re *regexp.Regexp
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is valid or not.
|
||||||
|
func (v *MatchRule) Validate(value interface{}) error {
|
||||||
|
value, isNil := Indirect(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isString, str, isBytes, bs := StringOrBytes(value)
|
||||||
|
if isString && (str == "" || v.re.MatchString(str)) {
|
||||||
|
return nil
|
||||||
|
} else if isBytes && (len(bs) == 0 || v.re.Match(bs)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New(v.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (v *MatchRule) Error(message string) *MatchRule {
|
||||||
|
v.message = message
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ThresholdRule struct {
|
||||||
|
threshold interface{}
|
||||||
|
operator int
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
greaterThan = iota
|
||||||
|
greaterEqualThan
|
||||||
|
lessThan
|
||||||
|
lessEqualThan
|
||||||
|
)
|
||||||
|
|
||||||
|
// Min is a validation rule that checks if a value is greater or equal than the specified value.
|
||||||
|
// By calling Exclusive, the rule will check if the value is strictly greater than the specified value.
|
||||||
|
// Note that the value being checked and the threshold value must be of the same type.
|
||||||
|
// Only int, uint, float and time.Time types are supported.
|
||||||
|
// An empty value is considered valid. Please use the Required rule to make sure a value is not empty.
|
||||||
|
func Min(min interface{}) *ThresholdRule {
|
||||||
|
return &ThresholdRule{
|
||||||
|
threshold: min,
|
||||||
|
operator: greaterEqualThan,
|
||||||
|
message: fmt.Sprintf("must be no less than %v", min),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max is a validation rule that checks if a value is less or equal than the specified value.
|
||||||
|
// By calling Exclusive, the rule will check if the value is strictly less than the specified value.
|
||||||
|
// Note that the value being checked and the threshold value must be of the same type.
|
||||||
|
// Only int, uint, float and time.Time types are supported.
|
||||||
|
// An empty value is considered valid. Please use the Required rule to make sure a value is not empty.
|
||||||
|
func Max(max interface{}) *ThresholdRule {
|
||||||
|
return &ThresholdRule{
|
||||||
|
threshold: max,
|
||||||
|
operator: lessEqualThan,
|
||||||
|
message: fmt.Sprintf("must be no greater than %v", max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclusive sets the comparison to exclude the boundary value.
|
||||||
|
func (r *ThresholdRule) Exclusive() *ThresholdRule {
|
||||||
|
if r.operator == greaterEqualThan {
|
||||||
|
r.operator = greaterThan
|
||||||
|
r.message = fmt.Sprintf("must be greater than %v", r.threshold)
|
||||||
|
} else if r.operator == lessEqualThan {
|
||||||
|
r.operator = lessThan
|
||||||
|
r.message = fmt.Sprintf("must be less than %v", r.threshold)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is valid or not.
|
||||||
|
func (r *ThresholdRule) Validate(value interface{}) error {
|
||||||
|
value, isNil := Indirect(value)
|
||||||
|
if isNil || IsEmpty(value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(r.threshold)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
v, err := ToInt(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.compareInt(rv.Int(), v) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
v, err := ToUint(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.compareUint(rv.Uint(), v) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
v, err := ToFloat(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.compareFloat(rv.Float(), v) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
t, ok := r.threshold.(time.Time)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("type not supported: %v", rv.Type())
|
||||||
|
}
|
||||||
|
v, ok := value.(time.Time)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("cannot convert %v to time.Time", reflect.TypeOf(value))
|
||||||
|
}
|
||||||
|
if v.IsZero() || r.compareTime(t, v) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("type not supported: %v", rv.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(r.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (r *ThresholdRule) Error(message string) *ThresholdRule {
|
||||||
|
r.message = message
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ThresholdRule) compareInt(threshold, value int64) bool {
|
||||||
|
switch r.operator {
|
||||||
|
case greaterThan:
|
||||||
|
return value > threshold
|
||||||
|
case greaterEqualThan:
|
||||||
|
return value >= threshold
|
||||||
|
case lessThan:
|
||||||
|
return value < threshold
|
||||||
|
default:
|
||||||
|
return value <= threshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ThresholdRule) compareUint(threshold, value uint64) bool {
|
||||||
|
switch r.operator {
|
||||||
|
case greaterThan:
|
||||||
|
return value > threshold
|
||||||
|
case greaterEqualThan:
|
||||||
|
return value >= threshold
|
||||||
|
case lessThan:
|
||||||
|
return value < threshold
|
||||||
|
default:
|
||||||
|
return value <= threshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ThresholdRule) compareFloat(threshold, value float64) bool {
|
||||||
|
switch r.operator {
|
||||||
|
case greaterThan:
|
||||||
|
return value > threshold
|
||||||
|
case greaterEqualThan:
|
||||||
|
return value >= threshold
|
||||||
|
case lessThan:
|
||||||
|
return value < threshold
|
||||||
|
default:
|
||||||
|
return value <= threshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ThresholdRule) compareTime(threshold, value time.Time) bool {
|
||||||
|
switch r.operator {
|
||||||
|
case greaterThan:
|
||||||
|
return value.After(threshold)
|
||||||
|
case greaterEqualThan:
|
||||||
|
return value.After(threshold) || value.Equal(threshold)
|
||||||
|
case lessThan:
|
||||||
|
return value.Before(threshold)
|
||||||
|
default:
|
||||||
|
return value.Before(threshold) || value.Equal(threshold)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MultipleOf(threshold interface{}) *multipleOfRule {
|
||||||
|
return &multipleOfRule{
|
||||||
|
threshold,
|
||||||
|
fmt.Sprintf("must be multiple of %v", threshold),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type multipleOfRule struct {
|
||||||
|
threshold interface{}
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (r *multipleOfRule) Error(message string) *multipleOfRule {
|
||||||
|
r.message = message
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (r *multipleOfRule) Validate(value interface{}) error {
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(r.threshold)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
v, err := ToInt(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if v%rv.Int() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
v, err := ToUint(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v%rv.Uint() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("type not supported: %v", rv.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(r.message)
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright 2018 Qiang Xue, Google LLC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotIn returns a validation rule that checks if a value os absent from, the given list of values.
|
||||||
|
// Note that the value being checked and the possible range of values must be of the same type.
|
||||||
|
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
|
||||||
|
func NotIn(values ...interface{}) *NotInRule {
|
||||||
|
return &NotInRule{
|
||||||
|
elements: values,
|
||||||
|
message: "must not be in list",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotInRule struct {
|
||||||
|
elements []interface{}
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is valid or not.
|
||||||
|
func (r *NotInRule) Validate(value interface{}) error {
|
||||||
|
value, isNil := Indirect(value)
|
||||||
|
if isNil || IsEmpty(value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range r.elements {
|
||||||
|
if e == value {
|
||||||
|
return errors.New(r.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (r *NotInRule) Error(message string) *NotInRule {
|
||||||
|
r.message = message
|
||||||
|
return r
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// NotNil is a validation rule that checks if a value is not nil.
|
||||||
|
// NotNil only handles types including interface, pointer, slice, and map.
|
||||||
|
// All other types are considered valid.
|
||||||
|
var NotNil = ¬NilRule{message: "is required"}
|
||||||
|
|
||||||
|
type notNilRule struct {
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is valid or not.
|
||||||
|
func (r *notNilRule) Validate(value interface{}) error {
|
||||||
|
_, isNil := Indirect(value)
|
||||||
|
if isNil {
|
||||||
|
return errors.New(r.message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (r *notNilRule) Error(message string) *notNilRule {
|
||||||
|
return ¬NilRule{
|
||||||
|
message: message,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Required is a validation rule that checks if a value is not empty.
|
||||||
|
// A value is considered not empty if
|
||||||
|
// - integer, float: not zero
|
||||||
|
// - bool: true
|
||||||
|
// - string, array, slice, map: len() > 0
|
||||||
|
// - interface, pointer: not nil and the referenced value is not empty
|
||||||
|
// - any other types
|
||||||
|
var Required = &requiredRule{message: "cannot be blank", skipNil: false}
|
||||||
|
|
||||||
|
// NilOrNotEmpty checks if a value is a nil pointer or a value that is not empty.
|
||||||
|
// NilOrNotEmpty differs from Required in that it treats a nil pointer as valid.
|
||||||
|
var NilOrNotEmpty = &requiredRule{message: "cannot be blank", skipNil: true}
|
||||||
|
|
||||||
|
type requiredRule struct {
|
||||||
|
message string
|
||||||
|
skipNil bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is valid or not.
|
||||||
|
func (v *requiredRule) Validate(value interface{}) error {
|
||||||
|
value, isNil := Indirect(value)
|
||||||
|
if v.skipNil && !isNil && IsEmpty(value) || !v.skipNil && (isNil || IsEmpty(value)) {
|
||||||
|
return errors.New(v.message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (v *requiredRule) Error(message string) *requiredRule {
|
||||||
|
return &requiredRule{
|
||||||
|
message: message,
|
||||||
|
skipNil: v.skipNil,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
type stringValidator func(string) bool
|
||||||
|
|
||||||
|
// StringRule is a rule that checks a string variable using a specified stringValidator.
|
||||||
|
type StringRule struct {
|
||||||
|
validate stringValidator
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringRule creates a new validation rule using a function that takes a string value and returns a bool.
|
||||||
|
// The rule returned will use the function to check if a given string or byte slice is valid or not.
|
||||||
|
// An empty value is considered to be valid. Please use the Required rule to make sure a value is not empty.
|
||||||
|
func NewStringRule(validator stringValidator, message string) *StringRule {
|
||||||
|
return &StringRule{
|
||||||
|
validate: validator,
|
||||||
|
message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sets the error message for the rule.
|
||||||
|
func (v *StringRule) Error(message string) *StringRule {
|
||||||
|
return NewStringRule(v.validate, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is valid or not.
|
||||||
|
func (v *StringRule) Validate(value interface{}) error {
|
||||||
|
value, isNil := Indirect(value)
|
||||||
|
if isNil || IsEmpty(value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, err := EnsureString(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.validate(str) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New(v.message)
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrStructPointer is the error that a struct being validated is not specified as a pointer.
|
||||||
|
ErrStructPointer = errors.New("only a pointer to a struct can be validated")
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ErrFieldPointer is the error that a field is not specified as a pointer.
|
||||||
|
ErrFieldPointer int
|
||||||
|
|
||||||
|
// ErrFieldNotFound is the error that a field cannot be found in the struct.
|
||||||
|
ErrFieldNotFound int
|
||||||
|
|
||||||
|
// FieldRules represents a rule set associated with a struct field.
|
||||||
|
FieldRules struct {
|
||||||
|
fieldPtr interface{}
|
||||||
|
rules []Rule
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error returns the error string of ErrFieldPointer.
|
||||||
|
func (e ErrFieldPointer) Error() string {
|
||||||
|
return fmt.Sprintf("field #%v must be specified as a pointer", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the error string of ErrFieldNotFound.
|
||||||
|
func (e ErrFieldNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("field #%v cannot be found in the struct", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateStruct validates a struct by checking the specified struct fields against the corresponding validation rules.
|
||||||
|
// Note that the struct being validated must be specified as a pointer to it. If the pointer is nil, it is considered valid.
|
||||||
|
// Use Field() to specify struct fields that need to be validated. Each Field() call specifies a single field which
|
||||||
|
// should be specified as a pointer to the field. A field can be associated with multiple rules.
|
||||||
|
// For example,
|
||||||
|
//
|
||||||
|
// value := struct {
|
||||||
|
// Name string
|
||||||
|
// Value string
|
||||||
|
// }{"name", "demo"}
|
||||||
|
// err := validation.ValidateStruct(&value,
|
||||||
|
// validation.Field(&a.Name, validation.Required),
|
||||||
|
// validation.Field(&a.Value, validation.Required, validation.Length(5, 10)),
|
||||||
|
// )
|
||||||
|
// fmt.Println(err)
|
||||||
|
// // Value: the length must be between 5 and 10.
|
||||||
|
//
|
||||||
|
// An error will be returned if validation fails.
|
||||||
|
func ValidateStruct(structPtr interface{}, fields ...*FieldRules) error {
|
||||||
|
value := reflect.ValueOf(structPtr)
|
||||||
|
if value.Kind() != reflect.Ptr || !value.IsNil() && value.Elem().Kind() != reflect.Struct {
|
||||||
|
// must be a pointer to a struct
|
||||||
|
return NewInternalError(ErrStructPointer)
|
||||||
|
}
|
||||||
|
if value.IsNil() {
|
||||||
|
// treat a nil struct pointer as valid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
value = value.Elem()
|
||||||
|
|
||||||
|
errs := Errors{}
|
||||||
|
|
||||||
|
for i, fr := range fields {
|
||||||
|
fv := reflect.ValueOf(fr.fieldPtr)
|
||||||
|
if fv.Kind() != reflect.Ptr {
|
||||||
|
return NewInternalError(ErrFieldPointer(i))
|
||||||
|
}
|
||||||
|
ft := findStructField(value, fv)
|
||||||
|
if ft == nil {
|
||||||
|
return NewInternalError(ErrFieldNotFound(i))
|
||||||
|
}
|
||||||
|
if err := Validate(fv.Elem().Interface(), fr.rules...); err != nil {
|
||||||
|
if ie, ok := err.(InternalError); ok && ie.InternalError() != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ft.Anonymous {
|
||||||
|
// merge errors from anonymous struct field
|
||||||
|
if es, ok := err.(Errors); ok {
|
||||||
|
for name, value := range es {
|
||||||
|
errs[name] = value
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errs[getErrorFieldName(ft)] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field specifies a struct field and the corresponding validation rules.
|
||||||
|
// The struct field must be specified as a pointer to it.
|
||||||
|
func Field(fieldPtr interface{}, rules ...Rule) *FieldRules {
|
||||||
|
return &FieldRules{
|
||||||
|
fieldPtr: fieldPtr,
|
||||||
|
rules: rules,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// findStructField looks for a field in the given struct.
|
||||||
|
// The field being looked for should be a pointer to the actual struct field.
|
||||||
|
// If found, the field info will be returned. Otherwise, nil will be returned.
|
||||||
|
func findStructField(structValue reflect.Value, fieldValue reflect.Value) *reflect.StructField {
|
||||||
|
ptr := fieldValue.Pointer()
|
||||||
|
for i := structValue.NumField() - 1; i >= 0; i-- {
|
||||||
|
sf := structValue.Type().Field(i)
|
||||||
|
if ptr == structValue.Field(i).UnsafeAddr() {
|
||||||
|
// do additional type comparison because it's possible that the address of
|
||||||
|
// an embedded struct is the same as the first field of the embedded struct
|
||||||
|
if sf.Type == fieldValue.Elem().Type() {
|
||||||
|
return &sf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sf.Anonymous {
|
||||||
|
// delve into anonymous struct to look for the field
|
||||||
|
fi := structValue.Field(i)
|
||||||
|
if sf.Type.Kind() == reflect.Ptr {
|
||||||
|
fi = fi.Elem()
|
||||||
|
}
|
||||||
|
if fi.Kind() == reflect.Struct {
|
||||||
|
if f := findStructField(fi, fieldValue); f != nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getErrorFieldName returns the name that should be used to represent the validation error of a struct field.
|
||||||
|
func getErrorFieldName(f *reflect.StructField) string {
|
||||||
|
if tag := f.Tag.Get(ErrorTag); tag != "" {
|
||||||
|
if cps := strings.SplitN(tag, ",", 2); cps[0] != "" {
|
||||||
|
return cps[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.Name
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bytesType = reflect.TypeOf([]byte(nil))
|
||||||
|
valuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnsureString ensures the given value is a string.
|
||||||
|
// If the value is a byte slice, it will be typecast into a string.
|
||||||
|
// An error is returned otherwise.
|
||||||
|
func EnsureString(value interface{}) (string, error) {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
if v.Kind() == reflect.String {
|
||||||
|
return v.String(), nil
|
||||||
|
}
|
||||||
|
if v.Type() == bytesType {
|
||||||
|
return string(v.Interface().([]byte)), nil
|
||||||
|
}
|
||||||
|
return "", errors.New("must be either a string or byte slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringOrBytes typecasts a value into a string or byte slice.
|
||||||
|
// Boolean flags are returned to indicate if the typecasting succeeds or not.
|
||||||
|
func StringOrBytes(value interface{}) (isString bool, str string, isBytes bool, bs []byte) {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
if v.Kind() == reflect.String {
|
||||||
|
str = v.String()
|
||||||
|
isString = true
|
||||||
|
} else if v.Kind() == reflect.Slice && v.Type() == bytesType {
|
||||||
|
bs = v.Interface().([]byte)
|
||||||
|
isBytes = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LengthOfValue returns the length of a value that is a string, slice, map, or array.
|
||||||
|
// An error is returned for all other types.
|
||||||
|
func LengthOfValue(value interface{}) (int, error) {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String, reflect.Slice, reflect.Map, reflect.Array:
|
||||||
|
return v.Len(), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot get the length of %v", v.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt converts the given value to an int64.
|
||||||
|
// An error is returned for all incompatible types.
|
||||||
|
func ToInt(value interface{}) (int64, error) {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int(), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot convert %v to int64", v.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint converts the given value to an uint64.
|
||||||
|
// An error is returned for all incompatible types.
|
||||||
|
func ToUint(value interface{}) (uint64, error) {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint(), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot convert %v to uint64", v.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat converts the given value to a float64.
|
||||||
|
// An error is returned for all incompatible types.
|
||||||
|
func ToFloat(value interface{}) (float64, error) {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float(), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot convert %v to float64", v.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty checks if a value is empty or not.
|
||||||
|
// A value is considered empty if
|
||||||
|
// - integer, float: zero
|
||||||
|
// - bool: false
|
||||||
|
// - string, array: len() == 0
|
||||||
|
// - slice, map: nil or len() == 0
|
||||||
|
// - interface, pointer: nil or the referenced value is empty
|
||||||
|
func IsEmpty(value interface{}) bool {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String, reflect.Array, reflect.Map, reflect.Slice:
|
||||||
|
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.Invalid:
|
||||||
|
return true
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return IsEmpty(v.Elem().Interface())
|
||||||
|
case reflect.Struct:
|
||||||
|
v, ok := value.(time.Time)
|
||||||
|
if ok && v.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indirect returns the value that the given interface or pointer references to.
|
||||||
|
// If the value implements driver.Valuer, it will deal with the value returned by
|
||||||
|
// the Value() method instead. A boolean value is also returned to indicate if
|
||||||
|
// the value is nil or not (only applicable to interface, pointer, map, and slice).
|
||||||
|
// If the value is neither an interface nor a pointer, it will be returned back.
|
||||||
|
func Indirect(value interface{}) (interface{}, bool) {
|
||||||
|
rv := reflect.ValueOf(value)
|
||||||
|
kind := rv.Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return nil, true
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
return Indirect(rv.Elem().Interface())
|
||||||
|
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rv.Type().Implements(valuerType) {
|
||||||
|
return indirectValuer(value.(driver.Valuer))
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func indirectValuer(valuer driver.Valuer) (interface{}, bool) {
|
||||||
|
if value, err := valuer.Value(); value != nil && err == nil {
|
||||||
|
return Indirect(value)
|
||||||
|
}
|
||||||
|
return nil, true
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package validation provides configurable and extensible rules for validating data of various types.
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Validatable is the interface indicating the type implementing it supports data validation.
|
||||||
|
Validatable interface {
|
||||||
|
// Validate validates the data and returns an error if validation fails.
|
||||||
|
Validate() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule represents a validation rule.
|
||||||
|
Rule interface {
|
||||||
|
// Validate validates a value and returns a value if validation fails.
|
||||||
|
Validate(value interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleFunc represents a validator function.
|
||||||
|
// You may wrap it as a Rule by calling By().
|
||||||
|
RuleFunc func(value interface{}) error
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrorTag is the struct tag name used to customize the error field name for a struct field.
|
||||||
|
ErrorTag = "json"
|
||||||
|
|
||||||
|
// Skip is a special validation rule that indicates all rules following it should be skipped.
|
||||||
|
Skip = &skipRule{}
|
||||||
|
|
||||||
|
validatableType = reflect.TypeOf((*Validatable)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate validates the given value and returns the validation error, if any.
|
||||||
|
//
|
||||||
|
// Validate performs validation using the following steps:
|
||||||
|
// - validate the value against the rules passed in as parameters
|
||||||
|
// - if the value is a map and the map values implement `Validatable`, call `Validate` of every map value
|
||||||
|
// - if the value is a slice or array whose values implement `Validatable`, call `Validate` of every element
|
||||||
|
func Validate(value interface{}, rules ...Rule) error {
|
||||||
|
for _, rule := range rules {
|
||||||
|
if _, ok := rule.(*skipRule); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := rule.Validate(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(value)
|
||||||
|
if (rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface) && rv.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := value.(Validatable); ok {
|
||||||
|
return v.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
if rv.Type().Elem().Implements(validatableType) {
|
||||||
|
return validateMap(rv)
|
||||||
|
}
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if rv.Type().Elem().Implements(validatableType) {
|
||||||
|
return validateSlice(rv)
|
||||||
|
}
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return Validate(rv.Elem().Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateMap validates a map of validatable elements
|
||||||
|
func validateMap(rv reflect.Value) error {
|
||||||
|
errs := Errors{}
|
||||||
|
for _, key := range rv.MapKeys() {
|
||||||
|
if mv := rv.MapIndex(key).Interface(); mv != nil {
|
||||||
|
if err := mv.(Validatable).Validate(); err != nil {
|
||||||
|
errs[fmt.Sprintf("%v", key.Interface())] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateMap validates a slice/array of validatable elements
|
||||||
|
func validateSlice(rv reflect.Value) error {
|
||||||
|
errs := Errors{}
|
||||||
|
l := rv.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
if ev := rv.Index(i).Interface(); ev != nil {
|
||||||
|
if err := ev.(Validatable).Validate(); err != nil {
|
||||||
|
errs[strconv.Itoa(i)] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type skipRule struct{}
|
||||||
|
|
||||||
|
func (r *skipRule) Validate(interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type inlineRule struct {
|
||||||
|
f RuleFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *inlineRule) Validate(value interface{}) error {
|
||||||
|
return r.f(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// By wraps a RuleFunc into a Rule.
|
||||||
|
func By(f RuleFunc) Rule {
|
||||||
|
return &inlineRule{f}
|
||||||
|
}
|
|
@ -3,11 +3,16 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"admin.go",
|
||||||
"backup.go",
|
"backup.go",
|
||||||
|
"block_volume.go",
|
||||||
"client.go",
|
"client.go",
|
||||||
"cluster.go",
|
"cluster.go",
|
||||||
|
"db.go",
|
||||||
"device.go",
|
"device.go",
|
||||||
|
"logging.go",
|
||||||
"node.go",
|
"node.go",
|
||||||
|
"operations.go",
|
||||||
"topology.go",
|
"topology.go",
|
||||||
"volume.go",
|
"volume.go",
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2018 The heketi Authors
|
||||||
|
//
|
||||||
|
// This file is licensed to you under your choice of the GNU Lesser
|
||||||
|
// General Public License, version 3 or any later version (LGPLv3 or
|
||||||
|
// later), as published by the Free Software Foundation,
|
||||||
|
// or under the Apache License, Version 2.0 <LICENSE-APACHE2 or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
//
|
||||||
|
// You may not use this file except in compliance with those terms.
|
||||||
|
//
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/heketi/heketi/pkg/glusterfs/api"
|
||||||
|
"github.com/heketi/heketi/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) AdminStatusGet() (*api.AdminStatus, error) {
|
||||||
|
req, err := http.NewRequest("GET", c.host+"/admin", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var as api.AdminStatus
|
||||||
|
err = utils.GetJsonFromResponse(r, &as)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &as, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AdminStatusSet(request *api.AdminStatus) error {
|
||||||
|
// Marshal request to JSON
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", c.host+"/admin", bytes.NewBuffer(buffer))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.StatusCode == http.StatusOK || r.StatusCode == http.StatusNoContent {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
|
@ -37,12 +37,12 @@ func (c *Client) BackupDb(w io.Writer) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read data from response
|
// Read data from response
|
||||||
defer r.Body.Close()
|
|
||||||
_, err = io.Copy(w, r.Body)
|
_, err = io.Copy(w, r.Body)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
160
vendor/github.com/heketi/heketi/client/api/go-client/block_volume.go
generated
vendored
Normal file
160
vendor/github.com/heketi/heketi/client/api/go-client/block_volume.go
generated
vendored
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2015 The heketi Authors
|
||||||
|
//
|
||||||
|
// This file is licensed to you under your choice of the GNU Lesser
|
||||||
|
// General Public License, version 3 or any later version (LGPLv3 or
|
||||||
|
// later), as published by the Free Software Foundation,
|
||||||
|
// or under the Apache License, Version 2.0 <LICENSE-APACHE2 or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
//
|
||||||
|
// You may not use this file except in compliance with those terms.
|
||||||
|
//
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/heketi/heketi/pkg/glusterfs/api"
|
||||||
|
"github.com/heketi/heketi/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) BlockVolumeCreate(request *api.BlockVolumeCreateRequest) (
|
||||||
|
*api.BlockVolumeInfoResponse, error) {
|
||||||
|
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST",
|
||||||
|
c.host+"/blockvolumes",
|
||||||
|
bytes.NewBuffer(buffer))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusAccepted {
|
||||||
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err = c.waitForResponseWithTimer(r, time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockvolume api.BlockVolumeInfoResponse
|
||||||
|
err = utils.GetJsonFromResponse(r, &blockvolume)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &blockvolume, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BlockVolumeList() (*api.BlockVolumeListResponse, error) {
|
||||||
|
req, err := http.NewRequest("GET", c.host+"/blockvolumes", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockvolumes api.BlockVolumeListResponse
|
||||||
|
err = utils.GetJsonFromResponse(r, &blockvolumes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &blockvolumes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BlockVolumeInfo(id string) (*api.BlockVolumeInfoResponse, error) {
|
||||||
|
req, err := http.NewRequest("GET", c.host+"/blockvolumes/"+id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockvolume api.BlockVolumeInfoResponse
|
||||||
|
err = utils.GetJsonFromResponse(r, &blockvolume)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &blockvolume, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BlockVolumeDelete(id string) error {
|
||||||
|
req, err := http.NewRequest("DELETE", c.host+"/blockvolumes/"+id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusAccepted {
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err = c.waitForResponseWithTimer(r, time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.StatusCode != http.StatusNoContent {
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -13,9 +13,16 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
|
@ -24,35 +31,118 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MAX_CONCURRENT_REQUESTS = 32
|
MAX_CONCURRENT_REQUESTS = 32
|
||||||
|
RETRY_COUNT = 6
|
||||||
|
|
||||||
|
// default delay values
|
||||||
|
MIN_DELAY = 10
|
||||||
|
MAX_DELAY = 30
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ClientTLSOptions struct {
|
||||||
|
// directly borrow the field names from crypto/tls
|
||||||
|
InsecureSkipVerify bool
|
||||||
|
// one or more cert file paths (best for self-signed certs)
|
||||||
|
VerifyCerts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client configuration options
|
||||||
|
type ClientOptions struct {
|
||||||
|
RetryEnabled bool
|
||||||
|
RetryCount int
|
||||||
|
// control waits between retries
|
||||||
|
RetryMinDelay, RetryMaxDelay int
|
||||||
|
}
|
||||||
|
|
||||||
// Client object
|
// Client object
|
||||||
type Client struct {
|
type Client struct {
|
||||||
host string
|
host string
|
||||||
key string
|
key string
|
||||||
user string
|
user string
|
||||||
throttle chan bool
|
throttle chan bool
|
||||||
|
|
||||||
|
// configuration for TLS support
|
||||||
|
tlsClientConfig *tls.Config
|
||||||
|
|
||||||
|
// general behavioral options
|
||||||
|
opts ClientOptions
|
||||||
|
|
||||||
|
// allow plugging in custom do wrappers
|
||||||
|
do func(*http.Request) (*http.Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new client to access a Heketi server
|
var defaultClientOptions = ClientOptions{
|
||||||
|
RetryEnabled: true,
|
||||||
|
RetryCount: RETRY_COUNT,
|
||||||
|
RetryMinDelay: MIN_DELAY,
|
||||||
|
RetryMaxDelay: MAX_DELAY,
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new client to access a Heketi server
|
||||||
func NewClient(host, user, key string) *Client {
|
func NewClient(host, user, key string) *Client {
|
||||||
|
return NewClientWithOptions(host, user, key, defaultClientOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientWithOptions creates a new client to access a Heketi server
|
||||||
|
// with a user specified suite of options.
|
||||||
|
func NewClientWithOptions(host, user, key string, opts ClientOptions) *Client {
|
||||||
c := &Client{}
|
c := &Client{}
|
||||||
|
|
||||||
c.key = key
|
c.key = key
|
||||||
c.host = host
|
c.host = host
|
||||||
c.user = user
|
c.user = user
|
||||||
|
c.opts = opts
|
||||||
// Maximum concurrent requests
|
// Maximum concurrent requests
|
||||||
c.throttle = make(chan bool, MAX_CONCURRENT_REQUESTS)
|
c.throttle = make(chan bool, MAX_CONCURRENT_REQUESTS)
|
||||||
|
if opts.RetryEnabled {
|
||||||
|
c.do = c.retryOperationDo
|
||||||
|
} else {
|
||||||
|
c.do = c.doBasic
|
||||||
|
}
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewClientTLS(host, user, key string, tlsOpts *ClientTLSOptions) (*Client, error) {
|
||||||
|
c := NewClient(host, user, key)
|
||||||
|
if err := c.SetTLSOptions(tlsOpts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create a client to access a Heketi server without authentication enabled
|
// Create a client to access a Heketi server without authentication enabled
|
||||||
func NewClientNoAuth(host string) *Client {
|
func NewClientNoAuth(host string) *Client {
|
||||||
return NewClient(host, "", "")
|
return NewClient(host, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTLSOptions configures an existing heketi client for
|
||||||
|
// TLS support based on the ClientTLSOptions.
|
||||||
|
func (c *Client) SetTLSOptions(o *ClientTLSOptions) error {
|
||||||
|
if o == nil {
|
||||||
|
c.tlsClientConfig = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{}
|
||||||
|
tlsConfig.InsecureSkipVerify = o.InsecureSkipVerify
|
||||||
|
if len(o.VerifyCerts) > 0 {
|
||||||
|
tlsConfig.RootCAs = x509.NewCertPool()
|
||||||
|
for _, path := range o.VerifyCerts {
|
||||||
|
pem, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read cert file %v: %v",
|
||||||
|
path, err)
|
||||||
|
}
|
||||||
|
if ok := tlsConfig.RootCAs.AppendCertsFromPEM(pem); !ok {
|
||||||
|
return fmt.Errorf("failed to load PEM encoded cert from %s",
|
||||||
|
path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.tlsClientConfig = tlsConfig
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Simple Hello test to check if the server is up
|
// Simple Hello test to check if the server is up
|
||||||
func (c *Client) Hello() error {
|
func (c *Client) Hello() error {
|
||||||
// Create request
|
// Create request
|
||||||
|
@ -72,6 +162,7 @@ func (c *Client) Hello() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -79,14 +170,20 @@ func (c *Client) Hello() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// doBasic performs the core http transaction.
|
||||||
// Make sure we do not run out of fds by throttling the requests
|
// Make sure we do not run out of fds by throttling the requests
|
||||||
func (c *Client) do(req *http.Request) (*http.Response, error) {
|
func (c *Client) doBasic(req *http.Request) (*http.Response, error) {
|
||||||
c.throttle <- true
|
c.throttle <- true
|
||||||
defer func() {
|
defer func() {
|
||||||
<-c.throttle
|
<-c.throttle
|
||||||
}()
|
}()
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := &http.Client{}
|
||||||
|
if c.tlsClientConfig != nil {
|
||||||
|
httpClient.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: c.tlsClientConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
httpClient.CheckRedirect = c.checkRedirect
|
httpClient.CheckRedirect = c.checkRedirect
|
||||||
return httpClient.Do(req)
|
return httpClient.Do(req)
|
||||||
}
|
}
|
||||||
|
@ -122,7 +219,7 @@ func (c *Client) waitForResponseWithTimer(r *http.Response,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for response
|
// Wait for response
|
||||||
r, err = c.do(req)
|
r, err = c.doBasic(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -132,6 +229,11 @@ func (c *Client) waitForResponseWithTimer(r *http.Response,
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
if r != nil {
|
||||||
|
//Read Response Body
|
||||||
|
ioutil.ReadAll(r.Body)
|
||||||
|
r.Body.Close()
|
||||||
|
}
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
} else {
|
} else {
|
||||||
return r, nil
|
return r, nil
|
||||||
|
@ -174,3 +276,66 @@ func (c *Client) setToken(r *http.Request) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// retryOperationDo performs the http request and internally
|
||||||
|
// handles http 429 codes up to the number of retries specified
|
||||||
|
// by the Client.
|
||||||
|
func (c *Client) retryOperationDo(req *http.Request) (*http.Response, error) {
|
||||||
|
var (
|
||||||
|
requestBody []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if req.Body != nil {
|
||||||
|
requestBody, err = ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
var r *http.Response
|
||||||
|
for i := 0; i <= c.opts.RetryCount; i++ {
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(requestBody))
|
||||||
|
r, err = c.doBasic(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch r.StatusCode {
|
||||||
|
case http.StatusTooManyRequests:
|
||||||
|
if r != nil {
|
||||||
|
//Read Response Body
|
||||||
|
// I don't like discarding error here, but I cant
|
||||||
|
// think of something better atm
|
||||||
|
b, _ := ioutil.ReadAll(r.Body)
|
||||||
|
r.Body.Close()
|
||||||
|
r.Body = ioutil.NopCloser(bytes.NewReader(b))
|
||||||
|
}
|
||||||
|
//sleep before continue
|
||||||
|
time.Sleep(c.opts.retryDelay(r))
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
return r, err
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryDelay returns a duration for which a retry should wait
|
||||||
|
// (after failure) before continuing.
|
||||||
|
func (c *ClientOptions) retryDelay(r *http.Response) time.Duration {
|
||||||
|
var (
|
||||||
|
min = c.RetryMinDelay
|
||||||
|
max = c.RetryMaxDelay
|
||||||
|
)
|
||||||
|
if ra := r.Header.Get("Retry-After"); ra != "" {
|
||||||
|
// TODO: support http date
|
||||||
|
if i, err := strconv.Atoi(ra); err == nil {
|
||||||
|
s := rand.Intn(min) + i
|
||||||
|
return time.Second * time.Duration(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s := rand.Intn(max-min) + min
|
||||||
|
return time.Second * time.Duration(s)
|
||||||
|
}
|
||||||
|
|
|
@ -14,16 +14,23 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/heketi/heketi/pkg/glusterfs/api"
|
"github.com/heketi/heketi/pkg/glusterfs/api"
|
||||||
"github.com/heketi/heketi/pkg/utils"
|
"github.com/heketi/heketi/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) ClusterCreate() (*api.ClusterInfoResponse, error) {
|
func (c *Client) ClusterCreate(request *api.ClusterCreateRequest) (*api.ClusterInfoResponse, error) {
|
||||||
|
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Create a request
|
// Create a request
|
||||||
req, err := http.NewRequest("POST", c.host+"/clusters", bytes.NewBuffer([]byte(`{}`)))
|
req, err := http.NewRequest("POST", c.host+"/clusters",
|
||||||
|
bytes.NewBuffer(buffer))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -40,6 +47,7 @@ func (c *Client) ClusterCreate() (*api.ClusterInfoResponse, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusCreated {
|
if r.StatusCode != http.StatusCreated {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -47,7 +55,6 @@ func (c *Client) ClusterCreate() (*api.ClusterInfoResponse, error) {
|
||||||
// Read JSON response
|
// Read JSON response
|
||||||
var cluster api.ClusterInfoResponse
|
var cluster api.ClusterInfoResponse
|
||||||
err = utils.GetJsonFromResponse(r, &cluster)
|
err = utils.GetJsonFromResponse(r, &cluster)
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -55,6 +62,40 @@ func (c *Client) ClusterCreate() (*api.ClusterInfoResponse, error) {
|
||||||
return &cluster, nil
|
return &cluster, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ClusterSetFlags(id string, request *api.ClusterSetFlagsRequest) error {
|
||||||
|
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a request
|
||||||
|
req, err := http.NewRequest("POST", c.host+"/clusters/"+id+"/flags",
|
||||||
|
bytes.NewBuffer(buffer))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ClusterInfo(id string) (*api.ClusterInfoResponse, error) {
|
func (c *Client) ClusterInfo(id string) (*api.ClusterInfoResponse, error) {
|
||||||
|
|
||||||
// Create request
|
// Create request
|
||||||
|
@ -74,6 +115,7 @@ func (c *Client) ClusterInfo(id string) (*api.ClusterInfoResponse, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +123,6 @@ func (c *Client) ClusterInfo(id string) (*api.ClusterInfoResponse, error) {
|
||||||
// Read JSON response
|
// Read JSON response
|
||||||
var cluster api.ClusterInfoResponse
|
var cluster api.ClusterInfoResponse
|
||||||
err = utils.GetJsonFromResponse(r, &cluster)
|
err = utils.GetJsonFromResponse(r, &cluster)
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -108,6 +149,7 @@ func (c *Client) ClusterList() (*api.ClusterListResponse, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -141,6 +183,7 @@ func (c *Client) ClusterDelete(id string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2018 The heketi Authors
|
||||||
|
//
|
||||||
|
// This file is licensed to you under your choice of the GNU Lesser
|
||||||
|
// General Public License, version 3 or any later version (LGPLv3 or
|
||||||
|
// later), as published by the Free Software Foundation,
|
||||||
|
// or under the Apache License, Version 2.0 <LICENSE-APACHE2 or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
//
|
||||||
|
// You may not use this file except in compliance with those terms.
|
||||||
|
//
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/heketi/heketi/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DbDump provides a JSON representation of current state of DB
|
||||||
|
func (c *Client) DbDump() (string, error) {
|
||||||
|
req, err := http.NewRequest("GET", c.host+"/db/dump", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return "", utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
respJSON := string(respBytes)
|
||||||
|
return respJSON, nil
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ package client
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ func (c *Client) DeviceAdd(request *api.DeviceAddRequest) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -82,6 +84,7 @@ func (c *Client) DeviceInfo(id string) (*api.DeviceInfoResponse, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -89,7 +92,6 @@ func (c *Client) DeviceInfo(id string) (*api.DeviceInfoResponse, error) {
|
||||||
// Read JSON response
|
// Read JSON response
|
||||||
var device api.DeviceInfoResponse
|
var device api.DeviceInfoResponse
|
||||||
err = utils.GetJsonFromResponse(r, &device)
|
err = utils.GetJsonFromResponse(r, &device)
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -98,9 +100,23 @@ func (c *Client) DeviceInfo(id string) (*api.DeviceInfoResponse, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DeviceDelete(id string) error {
|
func (c *Client) DeviceDelete(id string) error {
|
||||||
|
return c.DeviceDeleteWithOptions(id, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeviceDeleteWithOptions(
|
||||||
|
id string, request *api.DeviceDeleteOptions) error {
|
||||||
|
|
||||||
|
var buf io.Reader
|
||||||
|
if request != nil {
|
||||||
|
b, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf = bytes.NewBuffer(b)
|
||||||
|
}
|
||||||
|
|
||||||
// Create a request
|
// Create a request
|
||||||
req, err := http.NewRequest("DELETE", c.host+"/devices/"+id, nil)
|
req, err := http.NewRequest("DELETE", c.host+"/devices/"+id, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -116,6 +132,7 @@ func (c *Client) DeviceDelete(id string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -161,6 +178,7 @@ func (c *Client) DeviceState(id string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -176,3 +194,71 @@ func (c *Client) DeviceState(id string,
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeviceResync(id string) error {
|
||||||
|
|
||||||
|
// Create a request
|
||||||
|
req, err := http.NewRequest("GET", c.host+"/devices/"+id+"/resync", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusAccepted {
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for response
|
||||||
|
r, err = c.waitForResponseWithTimer(r, time.Millisecond*250)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.StatusCode != http.StatusNoContent {
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeviceSetTags(id string, request *api.TagsChangeRequest) error {
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST",
|
||||||
|
c.host+"/devices/"+id+"/tags",
|
||||||
|
bytes.NewBuffer(buffer))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get info
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2018 The heketi Authors
|
||||||
|
//
|
||||||
|
// This file is licensed to you under your choice of the GNU Lesser
|
||||||
|
// General Public License, version 3 or any later version (LGPLv3 or
|
||||||
|
// later), as published by the Free Software Foundation,
|
||||||
|
// or under the Apache License, Version 2.0 <LICENSE-APACHE2 or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
//
|
||||||
|
// You may not use this file except in compliance with those terms.
|
||||||
|
//
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/heketi/heketi/pkg/glusterfs/api"
|
||||||
|
"github.com/heketi/heketi/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) LogLevelGet() (*api.LogLevelInfo, error) {
|
||||||
|
req, err := http.NewRequest("GET", c.host+"/internal/logging", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var lli api.LogLevelInfo
|
||||||
|
err = utils.GetJsonFromResponse(r, &lli)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &lli, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) LogLevelSet(request *api.LogLevelInfo) error {
|
||||||
|
// Marshal request to JSON
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", c.host+"/internal/logging", bytes.NewBuffer(buffer))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ func (c *Client) NodeAdd(request *api.NodeAddRequest) (*api.NodeInfoResponse, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -64,7 +65,6 @@ func (c *Client) NodeAdd(request *api.NodeAddRequest) (*api.NodeInfoResponse, er
|
||||||
// Read JSON response
|
// Read JSON response
|
||||||
var node api.NodeInfoResponse
|
var node api.NodeInfoResponse
|
||||||
err = utils.GetJsonFromResponse(r, &node)
|
err = utils.GetJsonFromResponse(r, &node)
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ func (c *Client) NodeInfo(id string) (*api.NodeInfoResponse, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -98,7 +99,6 @@ func (c *Client) NodeInfo(id string) (*api.NodeInfoResponse, error) {
|
||||||
// Read JSON response
|
// Read JSON response
|
||||||
var node api.NodeInfoResponse
|
var node api.NodeInfoResponse
|
||||||
err = utils.GetJsonFromResponse(r, &node)
|
err = utils.GetJsonFromResponse(r, &node)
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -125,6 +125,7 @@ func (c *Client) NodeDelete(id string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -168,6 +169,7 @@ func (c *Client) NodeState(id string, request *api.StateRequest) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -183,3 +185,35 @@ func (c *Client) NodeState(id string, request *api.StateRequest) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) NodeSetTags(id string, request *api.TagsChangeRequest) error {
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST",
|
||||||
|
c.host+"/nodes/"+id+"/tags",
|
||||||
|
bytes.NewBuffer(buffer))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get info
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
46
vendor/github.com/heketi/heketi/client/api/go-client/operations.go
generated
vendored
Normal file
46
vendor/github.com/heketi/heketi/client/api/go-client/operations.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2018 The heketi Authors
|
||||||
|
//
|
||||||
|
// This file is licensed to you under your choice of the GNU Lesser
|
||||||
|
// General Public License, version 3 or any later version (LGPLv3 or
|
||||||
|
// later), as published by the Free Software Foundation,
|
||||||
|
// or under the Apache License, Version 2.0 <LICENSE-APACHE2 or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
//
|
||||||
|
// You may not use this file except in compliance with those terms.
|
||||||
|
//
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/heketi/heketi/pkg/glusterfs/api"
|
||||||
|
"github.com/heketi/heketi/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) OperationsInfo() (*api.OperationsInfo, error) {
|
||||||
|
req, err := http.NewRequest("GET", c.host+"/operations", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var oi api.OperationsInfo
|
||||||
|
err = utils.GetJsonFromResponse(r, &oi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oi, nil
|
||||||
|
}
|
|
@ -33,6 +33,10 @@ func (c *Client) TopologyInfo() (*api.TopologyInfoResponse, error) {
|
||||||
Id: clusteri.Id,
|
Id: clusteri.Id,
|
||||||
Volumes: make([]api.VolumeInfoResponse, 0),
|
Volumes: make([]api.VolumeInfoResponse, 0),
|
||||||
Nodes: make([]api.NodeInfoResponse, 0),
|
Nodes: make([]api.NodeInfoResponse, 0),
|
||||||
|
ClusterFlags: api.ClusterFlags{
|
||||||
|
Block: clusteri.Block,
|
||||||
|
File: clusteri.File,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
cluster.Id = clusteri.Id
|
cluster.Id = clusteri.Id
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,61 @@ func (c *Client) VolumeCreate(request *api.VolumeCreateRequest) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusAccepted {
|
||||||
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for response
|
||||||
|
r, err = c.waitForResponseWithTimer(r, time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read JSON response
|
||||||
|
var volume api.VolumeInfoResponse
|
||||||
|
err = utils.GetJsonFromResponse(r, &volume)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &volume, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) VolumeSetBlockRestriction(id string, request *api.VolumeBlockRestrictionRequest) (
|
||||||
|
*api.VolumeInfoResponse, error) {
|
||||||
|
|
||||||
|
// Marshal request to JSON
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a request
|
||||||
|
req, err := http.NewRequest("POST",
|
||||||
|
c.host+"/volumes/"+id+"/block-restriction",
|
||||||
|
bytes.NewBuffer(buffer))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -67,7 +122,6 @@ func (c *Client) VolumeCreate(request *api.VolumeCreateRequest) (
|
||||||
// Read JSON response
|
// Read JSON response
|
||||||
var volume api.VolumeInfoResponse
|
var volume api.VolumeInfoResponse
|
||||||
err = utils.GetJsonFromResponse(r, &volume)
|
err = utils.GetJsonFromResponse(r, &volume)
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -105,6 +159,7 @@ func (c *Client) VolumeExpand(id string, request *api.VolumeExpandRequest) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -121,7 +176,6 @@ func (c *Client) VolumeExpand(id string, request *api.VolumeExpandRequest) (
|
||||||
// Read JSON response
|
// Read JSON response
|
||||||
var volume api.VolumeInfoResponse
|
var volume api.VolumeInfoResponse
|
||||||
err = utils.GetJsonFromResponse(r, &volume)
|
err = utils.GetJsonFromResponse(r, &volume)
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -149,6 +203,7 @@ func (c *Client) VolumeList() (*api.VolumeListResponse, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -182,6 +237,7 @@ func (c *Client) VolumeInfo(id string) (*api.VolumeInfoResponse, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusOK {
|
if r.StatusCode != http.StatusOK {
|
||||||
return nil, utils.GetErrorFromResponse(r)
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -189,7 +245,6 @@ func (c *Client) VolumeInfo(id string) (*api.VolumeInfoResponse, error) {
|
||||||
// Read JSON response
|
// Read JSON response
|
||||||
var volume api.VolumeInfoResponse
|
var volume api.VolumeInfoResponse
|
||||||
err = utils.GetJsonFromResponse(r, &volume)
|
err = utils.GetJsonFromResponse(r, &volume)
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -216,6 +271,7 @@ func (c *Client) VolumeDelete(id string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
if r.StatusCode != http.StatusAccepted {
|
if r.StatusCode != http.StatusAccepted {
|
||||||
return utils.GetErrorFromResponse(r)
|
return utils.GetErrorFromResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -231,3 +287,52 @@ func (c *Client) VolumeDelete(id string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) VolumeClone(id string, request *api.VolumeCloneRequest) (*api.VolumeInfoResponse, error) {
|
||||||
|
// Marshal request to JSON
|
||||||
|
buffer, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a request
|
||||||
|
req, err := http.NewRequest("POST", c.host+"/volumes/"+id+"/clone", bytes.NewBuffer(buffer))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Set token
|
||||||
|
err = c.setToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
r, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if r.StatusCode != http.StatusAccepted {
|
||||||
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for response
|
||||||
|
r, err = c.waitForResponseWithTimer(r, time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return nil, utils.GetErrorFromResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read JSON response
|
||||||
|
var volume api.VolumeInfoResponse
|
||||||
|
err = utils.GetJsonFromResponse(r, &volume)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &volume, nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,10 @@ go_library(
|
||||||
importmap = "k8s.io/kubernetes/vendor/github.com/heketi/heketi/pkg/glusterfs/api",
|
importmap = "k8s.io/kubernetes/vendor/github.com/heketi/heketi/pkg/glusterfs/api",
|
||||||
importpath = "github.com/heketi/heketi/pkg/glusterfs/api",
|
importpath = "github.com/heketi/heketi/pkg/glusterfs/api",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/go-ozzo/ozzo-validation:go_default_library",
|
||||||
|
"//vendor/github.com/go-ozzo/ozzo-validation/is:go_default_library",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
|
|
|
@ -18,9 +18,38 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/go-ozzo/ozzo-validation"
|
||||||
|
"github.com/go-ozzo/ozzo-validation/is"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Restricting the deviceName to much smaller subset of Unix Path
|
||||||
|
// as unix path takes almost everything except NULL
|
||||||
|
deviceNameRe = regexp.MustCompile("^/[a-zA-Z0-9_.:/-]+$")
|
||||||
|
|
||||||
|
// Volume name constraints decided by looking at
|
||||||
|
// "cli_validate_volname" function in cli-cmd-parser.c of gluster code
|
||||||
|
volumeNameRe = regexp.MustCompile("^[a-zA-Z0-9_-]+$")
|
||||||
|
|
||||||
|
blockVolNameRe = regexp.MustCompile("^[a-zA-Z0-9_-]+$")
|
||||||
|
|
||||||
|
tagNameRe = regexp.MustCompile("^[a-zA-Z0-9_.-]+$")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateUUID is written this way because heketi UUID does not
|
||||||
|
// conform to neither UUID v4 nor v5.
|
||||||
|
func ValidateUUID(value interface{}) error {
|
||||||
|
s, _ := value.(string)
|
||||||
|
err := validation.Validate(s, validation.RuneLength(32, 32), is.Hexadecimal)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%v is not a valid UUID", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// State
|
// State
|
||||||
type EntryState string
|
type EntryState string
|
||||||
|
|
||||||
|
@ -31,6 +60,15 @@ const (
|
||||||
EntryStateFailed EntryState = "failed"
|
EntryStateFailed EntryState = "failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ValidateEntryState(value interface{}) error {
|
||||||
|
s, _ := value.(EntryState)
|
||||||
|
err := validation.Validate(s, validation.Required, validation.In(EntryStateOnline, EntryStateOffline, EntryStateFailed))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%v is not valid state", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type DurabilityType string
|
type DurabilityType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -39,11 +77,26 @@ const (
|
||||||
DurabilityEC DurabilityType = "disperse"
|
DurabilityEC DurabilityType = "disperse"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ValidateDurabilityType(value interface{}) error {
|
||||||
|
s, _ := value.(DurabilityType)
|
||||||
|
err := validation.Validate(s, validation.Required, validation.In(DurabilityReplicate, DurabilityDistributeOnly, DurabilityEC))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%v is not a valid durability type", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
type StateRequest struct {
|
type StateRequest struct {
|
||||||
State EntryState `json:"state"`
|
State EntryState `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (statereq StateRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&statereq,
|
||||||
|
validation.Field(&statereq.State, validation.Required, validation.By(ValidateEntryState)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Storage values in KB
|
// Storage values in KB
|
||||||
type StorageSize struct {
|
type StorageSize struct {
|
||||||
Total uint64 `json:"total"`
|
Total uint64 `json:"total"`
|
||||||
|
@ -56,6 +109,35 @@ type HostAddresses struct {
|
||||||
Storage sort.StringSlice `json:"storage"`
|
Storage sort.StringSlice `json:"storage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateManagementHostname(value interface{}) error {
|
||||||
|
s, _ := value.(sort.StringSlice)
|
||||||
|
for _, fqdn := range s {
|
||||||
|
err := validation.Validate(fqdn, validation.Required, is.Host)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%v is not a valid manage hostname", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateStorageHostname(value interface{}) error {
|
||||||
|
s, _ := value.(sort.StringSlice)
|
||||||
|
for _, ip := range s {
|
||||||
|
err := validation.Validate(ip, validation.Required, is.Host)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%v is not a valid storage hostname", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hostadd HostAddresses) Validate() error {
|
||||||
|
return validation.ValidateStruct(&hostadd,
|
||||||
|
validation.Field(&hostadd.Manage, validation.Required, validation.By(ValidateManagementHostname)),
|
||||||
|
validation.Field(&hostadd.Storage, validation.Required, validation.By(ValidateStorageHostname)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Brick
|
// Brick
|
||||||
type BrickInfo struct {
|
type BrickInfo struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
@ -70,12 +152,29 @@ type BrickInfo struct {
|
||||||
|
|
||||||
// Device
|
// Device
|
||||||
type Device struct {
|
type Device struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Tags map[string]string `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev Device) Validate() error {
|
||||||
|
return validation.ValidateStruct(&dev,
|
||||||
|
validation.Field(&dev.Name, validation.Required, validation.Match(deviceNameRe)),
|
||||||
|
validation.Field(&dev.Tags, validation.By(ValidateTags)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceAddRequest struct {
|
type DeviceAddRequest struct {
|
||||||
Device
|
Device
|
||||||
NodeId string `json:"node"`
|
NodeId string `json:"node"`
|
||||||
|
DestroyData bool `json:"destroydata,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devAddReq DeviceAddRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&devAddReq,
|
||||||
|
validation.Field(&devAddReq.Device, validation.Required),
|
||||||
|
validation.Field(&devAddReq.NodeId, validation.Required, validation.By(ValidateUUID)),
|
||||||
|
validation.Field(&devAddReq.DestroyData, validation.In(true, false)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceInfo struct {
|
type DeviceInfo struct {
|
||||||
|
@ -92,9 +191,19 @@ type DeviceInfoResponse struct {
|
||||||
|
|
||||||
// Node
|
// Node
|
||||||
type NodeAddRequest struct {
|
type NodeAddRequest struct {
|
||||||
Zone int `json:"zone"`
|
Zone int `json:"zone"`
|
||||||
Hostnames HostAddresses `json:"hostnames"`
|
Hostnames HostAddresses `json:"hostnames"`
|
||||||
ClusterId string `json:"cluster"`
|
ClusterId string `json:"cluster"`
|
||||||
|
Tags map[string]string `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req NodeAddRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&req,
|
||||||
|
validation.Field(&req.Zone, validation.Required, validation.Min(1)),
|
||||||
|
validation.Field(&req.Hostnames, validation.Required),
|
||||||
|
validation.Field(&req.ClusterId, validation.Required, validation.By(ValidateUUID)),
|
||||||
|
validation.Field(&req.Tags, validation.By(ValidateTags)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeInfo struct {
|
type NodeInfo struct {
|
||||||
|
@ -109,20 +218,37 @@ type NodeInfoResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cluster
|
// Cluster
|
||||||
|
|
||||||
|
type ClusterFlags struct {
|
||||||
|
Block bool `json:"block"`
|
||||||
|
File bool `json:"file"`
|
||||||
|
}
|
||||||
|
|
||||||
type Cluster struct {
|
type Cluster struct {
|
||||||
Volumes []VolumeInfoResponse `json:"volumes"`
|
Volumes []VolumeInfoResponse `json:"volumes"`
|
||||||
Nodes []NodeInfoResponse `json:"nodes"`
|
Nodes []NodeInfoResponse `json:"nodes"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
ClusterFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
type TopologyInfoResponse struct {
|
type TopologyInfoResponse struct {
|
||||||
ClusterList []Cluster `json:"clusters"`
|
ClusterList []Cluster `json:"clusters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClusterCreateRequest struct {
|
||||||
|
ClusterFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterSetFlagsRequest struct {
|
||||||
|
ClusterFlags
|
||||||
|
}
|
||||||
|
|
||||||
type ClusterInfoResponse struct {
|
type ClusterInfoResponse struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Nodes sort.StringSlice `json:"nodes"`
|
Nodes sort.StringSlice `json:"nodes"`
|
||||||
Volumes sort.StringSlice `json:"volumes"`
|
Volumes sort.StringSlice `json:"volumes"`
|
||||||
|
ClusterFlags
|
||||||
|
BlockVolumes sort.StringSlice `json:"blockvolumes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClusterListResponse struct {
|
type ClusterListResponse struct {
|
||||||
|
@ -147,19 +273,57 @@ type VolumeDurabilityInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type VolumeCreateRequest struct {
|
type VolumeCreateRequest struct {
|
||||||
// Size in GB
|
// Size in GiB
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
Clusters []string `json:"clusters,omitempty"`
|
Clusters []string `json:"clusters,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Durability VolumeDurabilityInfo `json:"durability,omitempty"`
|
Durability VolumeDurabilityInfo `json:"durability,omitempty"`
|
||||||
Gid int64 `json:"gid,omitempty"`
|
Gid int64 `json:"gid,omitempty"`
|
||||||
GlusterVolumeOptions []string `json:"glustervolumeoptions,omitempty"`
|
GlusterVolumeOptions []string `json:"glustervolumeoptions,omitempty"`
|
||||||
|
Block bool `json:"block,omitempty"`
|
||||||
Snapshot struct {
|
Snapshot struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Factor float32 `json:"factor"`
|
Factor float32 `json:"factor"`
|
||||||
} `json:"snapshot"`
|
} `json:"snapshot"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (volCreateRequest VolumeCreateRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&volCreateRequest,
|
||||||
|
validation.Field(&volCreateRequest.Size, validation.Required, validation.Min(1)),
|
||||||
|
validation.Field(&volCreateRequest.Clusters, validation.By(ValidateUUID)),
|
||||||
|
validation.Field(&volCreateRequest.Name, validation.Match(volumeNameRe)),
|
||||||
|
validation.Field(&volCreateRequest.Durability, validation.Skip),
|
||||||
|
validation.Field(&volCreateRequest.Gid, validation.Skip),
|
||||||
|
validation.Field(&volCreateRequest.GlusterVolumeOptions, validation.Skip),
|
||||||
|
validation.Field(&volCreateRequest.Block, validation.In(true, false)),
|
||||||
|
// This is possibly a bug in validation lib, ignore next two lines for now
|
||||||
|
// validation.Field(&volCreateRequest.Snapshot.Enable, validation.In(true, false)),
|
||||||
|
// validation.Field(&volCreateRequest.Snapshot.Factor, validation.Min(1.0)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockRestriction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unrestricted BlockRestriction = ""
|
||||||
|
Locked BlockRestriction = "locked"
|
||||||
|
LockedByUpdate BlockRestriction = "locked-by-update"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (br BlockRestriction) String() string {
|
||||||
|
switch br {
|
||||||
|
case Unrestricted:
|
||||||
|
return "(none)"
|
||||||
|
case Locked:
|
||||||
|
return "locked"
|
||||||
|
case LockedByUpdate:
|
||||||
|
return "locked-by-update"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type VolumeInfo struct {
|
type VolumeInfo struct {
|
||||||
VolumeCreateRequest
|
VolumeCreateRequest
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
@ -171,6 +335,12 @@ type VolumeInfo struct {
|
||||||
Options map[string]string `json:"options"`
|
Options map[string]string `json:"options"`
|
||||||
} `json:"glusterfs"`
|
} `json:"glusterfs"`
|
||||||
} `json:"mount"`
|
} `json:"mount"`
|
||||||
|
BlockInfo struct {
|
||||||
|
FreeSize int `json:"freesize,omitempty"`
|
||||||
|
ReservedSize int `json:"reservedsize,omitempty"`
|
||||||
|
BlockVolumes sort.StringSlice `json:"blockvolume,omitempty"`
|
||||||
|
Restriction BlockRestriction `json:"restriction,omitempty"`
|
||||||
|
} `json:"blockinfo,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VolumeInfoResponse struct {
|
type VolumeInfoResponse struct {
|
||||||
|
@ -186,6 +356,132 @@ type VolumeExpandRequest struct {
|
||||||
Size int `json:"expand_size"`
|
Size int `json:"expand_size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (volExpandReq VolumeExpandRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&volExpandReq,
|
||||||
|
validation.Field(&volExpandReq.Size, validation.Required, validation.Min(1)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VolumeCloneRequest struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vcr VolumeCloneRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&vcr,
|
||||||
|
validation.Field(&vcr.Name, validation.Match(volumeNameRe)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VolumeBlockRestrictionRequest struct {
|
||||||
|
Restriction BlockRestriction `json:"restriction"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vbrr VolumeBlockRestrictionRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&vbrr,
|
||||||
|
validation.Field(&vbrr.Restriction,
|
||||||
|
validation.In(Unrestricted, Locked)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockVolume
|
||||||
|
|
||||||
|
type BlockVolumeCreateRequest struct {
|
||||||
|
// Size in GiB
|
||||||
|
Size int `json:"size"`
|
||||||
|
Clusters []string `json:"clusters,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Hacount int `json:"hacount,omitempty"`
|
||||||
|
Auth bool `json:"auth,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (blockVolCreateReq BlockVolumeCreateRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&blockVolCreateReq,
|
||||||
|
validation.Field(&blockVolCreateReq.Size, validation.Required, validation.Min(1)),
|
||||||
|
validation.Field(&blockVolCreateReq.Clusters, validation.By(ValidateUUID)),
|
||||||
|
validation.Field(&blockVolCreateReq.Name, validation.Match(blockVolNameRe)),
|
||||||
|
validation.Field(&blockVolCreateReq.Hacount, validation.Min(1)),
|
||||||
|
validation.Field(&blockVolCreateReq.Auth, validation.Skip),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockVolumeInfo struct {
|
||||||
|
BlockVolumeCreateRequest
|
||||||
|
Id string `json:"id"`
|
||||||
|
BlockVolume struct {
|
||||||
|
Hosts []string `json:"hosts"`
|
||||||
|
Iqn string `json:"iqn"`
|
||||||
|
Lun int `json:"lun"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
/*
|
||||||
|
Options map[string]string `json:"options"` // needed?...
|
||||||
|
*/
|
||||||
|
} `json:"blockvolume"`
|
||||||
|
Cluster string `json:"cluster,omitempty"`
|
||||||
|
BlockHostingVolume string `json:"blockhostingvolume,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockVolumeInfoResponse struct {
|
||||||
|
BlockVolumeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockVolumeListResponse struct {
|
||||||
|
BlockVolumes []string `json:"blockvolumes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogLevelInfo struct {
|
||||||
|
// should contain one or more logger to log-level-name mapping
|
||||||
|
LogLevel map[string]string `json:"loglevel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TagsChangeType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnknownTagsChangeType TagsChangeType = ""
|
||||||
|
SetTags TagsChangeType = "set"
|
||||||
|
UpdateTags TagsChangeType = "update"
|
||||||
|
DeleteTags TagsChangeType = "delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common tag post body
|
||||||
|
type TagsChangeRequest struct {
|
||||||
|
Tags map[string]string `json:"tags"`
|
||||||
|
Change TagsChangeType `json:"change_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tcr TagsChangeRequest) Validate() error {
|
||||||
|
return validation.ValidateStruct(&tcr,
|
||||||
|
validation.Field(&tcr.Tags, validation.By(ValidateTags)),
|
||||||
|
validation.Field(&tcr.Change,
|
||||||
|
validation.Required,
|
||||||
|
validation.In(SetTags, UpdateTags, DeleteTags)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateTags(v interface{}) error {
|
||||||
|
t, ok := v.(map[string]string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("tags must be a map of strings to strings")
|
||||||
|
}
|
||||||
|
if len(t) > 32 {
|
||||||
|
return fmt.Errorf("too many tags specified (%v), up to %v supported",
|
||||||
|
len(t), 32)
|
||||||
|
}
|
||||||
|
for k, v := range t {
|
||||||
|
if len(k) == 0 {
|
||||||
|
return fmt.Errorf("tag names may not be empty")
|
||||||
|
}
|
||||||
|
if err := validation.Validate(k, validation.RuneLength(1, 32)); err != nil {
|
||||||
|
return fmt.Errorf("tag name %v: %v", k, err)
|
||||||
|
}
|
||||||
|
if err := validation.Validate(v, validation.RuneLength(0, 64)); err != nil {
|
||||||
|
return fmt.Errorf("value of tag %v: %v", k, err)
|
||||||
|
}
|
||||||
|
if !tagNameRe.MatchString(k) {
|
||||||
|
return fmt.Errorf("invalid characters in tag name %+v", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
func NewVolumeInfoResponse() *VolumeInfoResponse {
|
func NewVolumeInfoResponse() *VolumeInfoResponse {
|
||||||
|
@ -205,6 +501,11 @@ func (v *VolumeInfoResponse) String() string {
|
||||||
"Cluster Id: %v\n"+
|
"Cluster Id: %v\n"+
|
||||||
"Mount: %v\n"+
|
"Mount: %v\n"+
|
||||||
"Mount Options: backup-volfile-servers=%v\n"+
|
"Mount Options: backup-volfile-servers=%v\n"+
|
||||||
|
"Block: %v\n"+
|
||||||
|
"Free Size: %v\n"+
|
||||||
|
"Reserved Size: %v\n"+
|
||||||
|
"Block Hosting Restriction: %v\n"+
|
||||||
|
"Block Volumes: %v\n"+
|
||||||
"Durability Type: %v\n",
|
"Durability Type: %v\n",
|
||||||
v.Name,
|
v.Name,
|
||||||
v.Size,
|
v.Size,
|
||||||
|
@ -212,6 +513,11 @@ func (v *VolumeInfoResponse) String() string {
|
||||||
v.Cluster,
|
v.Cluster,
|
||||||
v.Mount.GlusterFS.MountPoint,
|
v.Mount.GlusterFS.MountPoint,
|
||||||
v.Mount.GlusterFS.Options["backup-volfile-servers"],
|
v.Mount.GlusterFS.Options["backup-volfile-servers"],
|
||||||
|
v.Block,
|
||||||
|
v.BlockInfo.FreeSize,
|
||||||
|
v.BlockInfo.ReservedSize,
|
||||||
|
v.BlockInfo.Restriction,
|
||||||
|
v.BlockInfo.BlockVolumes,
|
||||||
v.Durability.Type)
|
v.Durability.Type)
|
||||||
|
|
||||||
switch v.Durability.Type {
|
switch v.Durability.Type {
|
||||||
|
@ -248,3 +554,90 @@ func (v *VolumeInfoResponse) String() string {
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewBlockVolumeInfoResponse() *BlockVolumeInfoResponse {
|
||||||
|
|
||||||
|
info := &BlockVolumeInfoResponse{}
|
||||||
|
// Nothing to Construct now maybe for future
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// String functions
|
||||||
|
func (v *BlockVolumeInfoResponse) String() string {
|
||||||
|
s := fmt.Sprintf("Name: %v\n"+
|
||||||
|
"Size: %v\n"+
|
||||||
|
"Volume Id: %v\n"+
|
||||||
|
"Cluster Id: %v\n"+
|
||||||
|
"Hosts: %v\n"+
|
||||||
|
"IQN: %v\n"+
|
||||||
|
"LUN: %v\n"+
|
||||||
|
"Hacount: %v\n"+
|
||||||
|
"Username: %v\n"+
|
||||||
|
"Password: %v\n"+
|
||||||
|
"Block Hosting Volume: %v\n",
|
||||||
|
v.Name,
|
||||||
|
v.Size,
|
||||||
|
v.Id,
|
||||||
|
v.Cluster,
|
||||||
|
v.BlockVolume.Hosts,
|
||||||
|
v.BlockVolume.Iqn,
|
||||||
|
v.BlockVolume.Lun,
|
||||||
|
v.Hacount,
|
||||||
|
v.BlockVolume.Username,
|
||||||
|
v.BlockVolume.Password,
|
||||||
|
v.BlockHostingVolume)
|
||||||
|
|
||||||
|
/*
|
||||||
|
s += "\nBricks:\n"
|
||||||
|
for _, b := range v.Bricks {
|
||||||
|
s += fmt.Sprintf("Id: %v\n"+
|
||||||
|
"Path: %v\n"+
|
||||||
|
"Size (GiB): %v\n"+
|
||||||
|
"Node: %v\n"+
|
||||||
|
"Device: %v\n\n",
|
||||||
|
b.Id,
|
||||||
|
b.Path,
|
||||||
|
b.Size/(1024*1024),
|
||||||
|
b.NodeId,
|
||||||
|
b.DeviceId)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type OperationsInfo struct {
|
||||||
|
Total uint64 `json:"total"`
|
||||||
|
InFlight uint64 `json:"in_flight"`
|
||||||
|
// state based counts:
|
||||||
|
Stale uint64 `json:"stale"`
|
||||||
|
New uint64 `json:"new"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AdminStateNormal AdminState = "normal"
|
||||||
|
AdminStateReadOnly AdminState = "read-only"
|
||||||
|
AdminStateLocal AdminState = "local-client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminStatus struct {
|
||||||
|
State AdminState `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as AdminStatus) Validate() error {
|
||||||
|
return validation.ValidateStruct(&as,
|
||||||
|
validation.Field(&as.State,
|
||||||
|
validation.Required,
|
||||||
|
validation.In(AdminStateNormal, AdminStateReadOnly, AdminStateLocal)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceDeleteOptions is used to specify additional behavior for device
|
||||||
|
// deletes.
|
||||||
|
type DeviceDeleteOptions struct {
|
||||||
|
// force heketi to forget about a device, possibly
|
||||||
|
// orphaning metadata on the node
|
||||||
|
ForceForget bool `json:"forceforget"`
|
||||||
|
}
|
||||||
|
|
|
@ -5,17 +5,11 @@ go_library(
|
||||||
srcs = [
|
srcs = [
|
||||||
"bodystring.go",
|
"bodystring.go",
|
||||||
"jsonutils.go",
|
"jsonutils.go",
|
||||||
"log.go",
|
|
||||||
"sortedstrings.go",
|
|
||||||
"statusgroup.go",
|
"statusgroup.go",
|
||||||
"stringset.go",
|
|
||||||
"stringstack.go",
|
|
||||||
"uuid.go",
|
|
||||||
],
|
],
|
||||||
importmap = "k8s.io/kubernetes/vendor/github.com/heketi/heketi/pkg/utils",
|
importmap = "k8s.io/kubernetes/vendor/github.com/heketi/heketi/pkg/utils",
|
||||||
importpath = "github.com/heketi/heketi/pkg/utils",
|
importpath = "github.com/heketi/heketi/pkg/utils",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = ["//vendor/github.com/lpabon/godbc:go_default_library"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
|
|
|
@ -3,14 +3,18 @@
|
||||||
//
|
//
|
||||||
// This file is licensed to you under your choice of the GNU Lesser
|
// This file is licensed to you under your choice of the GNU Lesser
|
||||||
// General Public License, version 3 or any later version (LGPLv3 or
|
// General Public License, version 3 or any later version (LGPLv3 or
|
||||||
// later), or the GNU General Public License, version 2 (GPLv2), in all
|
// later), as published by the Free Software Foundation,
|
||||||
// cases as published by the Free Software Foundation.
|
// or under the Apache License, Version 2.0 <LICENSE-APACHE2 or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
//
|
||||||
|
// You may not use this file except in compliance with those terms.
|
||||||
//
|
//
|
||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -20,10 +24,10 @@ import (
|
||||||
// Return the body from a response as a string
|
// Return the body from a response as a string
|
||||||
func GetStringFromResponse(r *http.Response) (string, error) {
|
func GetStringFromResponse(r *http.Response) (string, error) {
|
||||||
body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
defer r.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
r.Body.Close()
|
|
||||||
return string(body), nil
|
return string(body), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,5 +37,10 @@ func GetErrorFromResponse(r *http.Response) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return errors.New(strings.TrimSpace(s))
|
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if len(s) == 0 {
|
||||||
|
return fmt.Errorf("server did not provide a message (status %v: %v)", r.StatusCode, http.StatusText(r.StatusCode))
|
||||||
|
}
|
||||||
|
return errors.New(s)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
//
|
//
|
||||||
// This file is licensed to you under your choice of the GNU Lesser
|
// This file is licensed to you under your choice of the GNU Lesser
|
||||||
// General Public License, version 3 or any later version (LGPLv3 or
|
// General Public License, version 3 or any later version (LGPLv3 or
|
||||||
// later), or the GNU General Public License, version 2 (GPLv2), in all
|
// later), as published by the Free Software Foundation,
|
||||||
// cases as published by the Free Software Foundation.
|
// or under the Apache License, Version 2.0 <LICENSE-APACHE2 or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
//
|
||||||
|
// You may not use this file except in compliance with those terms.
|
||||||
//
|
//
|
||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
|
@ -1,155 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) 2015 The heketi Authors
|
|
||||||
//
|
|
||||||
// This file is licensed to you under your choice of the GNU Lesser
|
|
||||||
// General Public License, version 3 or any later version (LGPLv3 or
|
|
||||||
// later), or the GNU General Public License, version 2 (GPLv2), in all
|
|
||||||
// cases as published by the Free Software Foundation.
|
|
||||||
//
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/lpabon/godbc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LogLevel int
|
|
||||||
|
|
||||||
// Log levels
|
|
||||||
const (
|
|
||||||
LEVEL_NOLOG LogLevel = iota
|
|
||||||
LEVEL_CRITICAL
|
|
||||||
LEVEL_ERROR
|
|
||||||
LEVEL_WARNING
|
|
||||||
LEVEL_INFO
|
|
||||||
LEVEL_DEBUG
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
stderr io.Writer = os.Stderr
|
|
||||||
stdout io.Writer = os.Stdout
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
critlog, errorlog, infolog *log.Logger
|
|
||||||
debuglog, warninglog *log.Logger
|
|
||||||
|
|
||||||
level LogLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
func logWithLongFile(l *log.Logger, format string, v ...interface{}) {
|
|
||||||
_, file, line, _ := runtime.Caller(2)
|
|
||||||
|
|
||||||
// Shorten the path.
|
|
||||||
// From
|
|
||||||
// /builddir/build/BUILD/heketi-3f4a5b1b6edff87232e8b24533c53b4151ebd9c7/src/github.com/heketi/heketi/apps/glusterfs/volume_entry.go
|
|
||||||
// to
|
|
||||||
// src/github.com/heketi/heketi/apps/glusterfs/volume_entry.go
|
|
||||||
i := strings.Index(file, "/src/")
|
|
||||||
if i == -1 {
|
|
||||||
i = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Print(fmt.Sprintf("%v:%v: ", file[i:], line) +
|
|
||||||
fmt.Sprintf(format, v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new logger
|
|
||||||
func NewLogger(prefix string, level LogLevel) *Logger {
|
|
||||||
godbc.Require(level >= 0, level)
|
|
||||||
godbc.Require(level <= LEVEL_DEBUG, level)
|
|
||||||
|
|
||||||
l := &Logger{}
|
|
||||||
|
|
||||||
if level == LEVEL_NOLOG {
|
|
||||||
l.level = LEVEL_DEBUG
|
|
||||||
} else {
|
|
||||||
l.level = level
|
|
||||||
}
|
|
||||||
|
|
||||||
l.critlog = log.New(stderr, prefix+" CRITICAL ", log.LstdFlags)
|
|
||||||
l.errorlog = log.New(stderr, prefix+" ERROR ", log.LstdFlags)
|
|
||||||
l.warninglog = log.New(stdout, prefix+" WARNING ", log.LstdFlags)
|
|
||||||
l.infolog = log.New(stdout, prefix+" INFO ", log.LstdFlags)
|
|
||||||
l.debuglog = log.New(stdout, prefix+" DEBUG ", log.LstdFlags)
|
|
||||||
|
|
||||||
godbc.Ensure(l.critlog != nil)
|
|
||||||
godbc.Ensure(l.errorlog != nil)
|
|
||||||
godbc.Ensure(l.warninglog != nil)
|
|
||||||
godbc.Ensure(l.infolog != nil)
|
|
||||||
godbc.Ensure(l.debuglog != nil)
|
|
||||||
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return current level
|
|
||||||
func (l *Logger) Level() LogLevel {
|
|
||||||
return l.level
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set level
|
|
||||||
func (l *Logger) SetLevel(level LogLevel) {
|
|
||||||
l.level = level
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log critical information
|
|
||||||
func (l *Logger) Critical(format string, v ...interface{}) {
|
|
||||||
if l.level >= LEVEL_CRITICAL {
|
|
||||||
logWithLongFile(l.critlog, format, v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log error string
|
|
||||||
func (l *Logger) LogError(format string, v ...interface{}) error {
|
|
||||||
if l.level >= LEVEL_ERROR {
|
|
||||||
logWithLongFile(l.errorlog, format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf(format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log error variable
|
|
||||||
func (l *Logger) Err(err error) error {
|
|
||||||
if l.level >= LEVEL_ERROR {
|
|
||||||
logWithLongFile(l.errorlog, "%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log warning information
|
|
||||||
func (l *Logger) Warning(format string, v ...interface{}) {
|
|
||||||
if l.level >= LEVEL_WARNING {
|
|
||||||
l.warninglog.Printf(format, v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log error variable as a warning
|
|
||||||
func (l *Logger) WarnErr(err error) error {
|
|
||||||
if l.level >= LEVEL_WARNING {
|
|
||||||
logWithLongFile(l.warninglog, "%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log string
|
|
||||||
func (l *Logger) Info(format string, v ...interface{}) {
|
|
||||||
if l.level >= LEVEL_INFO {
|
|
||||||
l.infolog.Printf(format, v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log string as debug
|
|
||||||
func (l *Logger) Debug(format string, v ...interface{}) {
|
|
||||||
if l.level >= LEVEL_DEBUG {
|
|
||||||
logWithLongFile(l.debuglog, format, v...)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) 2015 The heketi Authors
|
|
||||||
//
|
|
||||||
// This file is licensed to you under your choice of the GNU Lesser
|
|
||||||
// General Public License, version 3 or any later version (LGPLv3 or
|
|
||||||
// later), or the GNU General Public License, version 2 (GPLv2), in all
|
|
||||||
// cases as published by the Free Software Foundation.
|
|
||||||
//
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if a sorted string list has a string
|
|
||||||
func SortedStringHas(s sort.StringSlice, x string) bool {
|
|
||||||
index := s.Search(x)
|
|
||||||
if index == len(s) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s[s.Search(x)] == x
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a string from a sorted string list
|
|
||||||
func SortedStringsDelete(s sort.StringSlice, x string) sort.StringSlice {
|
|
||||||
index := s.Search(x)
|
|
||||||
if len(s) != index && s[index] == x {
|
|
||||||
s = append(s[:index], s[index+1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
|
@ -3,8 +3,11 @@
|
||||||
//
|
//
|
||||||
// This file is licensed to you under your choice of the GNU Lesser
|
// This file is licensed to you under your choice of the GNU Lesser
|
||||||
// General Public License, version 3 or any later version (LGPLv3 or
|
// General Public License, version 3 or any later version (LGPLv3 or
|
||||||
// later), or the GNU General Public License, version 2 (GPLv2), in all
|
// later), as published by the Free Software Foundation,
|
||||||
// cases as published by the Free Software Foundation.
|
// or under the Apache License, Version 2.0 <LICENSE-APACHE2 or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
//
|
||||||
|
// You may not use this file except in compliance with those terms.
|
||||||
//
|
//
|
||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) 2015 The heketi Authors
|
|
||||||
//
|
|
||||||
// This file is licensed to you under your choice of the GNU Lesser
|
|
||||||
// General Public License, version 3 or any later version (LGPLv3 or
|
|
||||||
// later), or the GNU General Public License, version 2 (GPLv2), in all
|
|
||||||
// cases as published by the Free Software Foundation.
|
|
||||||
//
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StringSet struct {
|
|
||||||
Set sort.StringSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a string set.
|
|
||||||
//
|
|
||||||
// A string set is a list where each element appears only once
|
|
||||||
func NewStringSet() *StringSet {
|
|
||||||
return &StringSet{
|
|
||||||
Set: make(sort.StringSlice, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a string to the string set
|
|
||||||
func (s *StringSet) Add(v string) {
|
|
||||||
if !SortedStringHas(s.Set, v) {
|
|
||||||
s.Set = append(s.Set, v)
|
|
||||||
s.Set.Sort()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return string list
|
|
||||||
func (s *StringSet) Strings() []string {
|
|
||||||
return s.Set
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StringSet) Len() int {
|
|
||||||
return len(s.Set)
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) 2015 The heketi Authors
|
|
||||||
//
|
|
||||||
// This file is licensed to you under your choice of the GNU Lesser
|
|
||||||
// General Public License, version 3 or any later version (LGPLv3 or
|
|
||||||
// later), or the GNU General Public License, version 2 (GPLv2), in all
|
|
||||||
// cases as published by the Free Software Foundation.
|
|
||||||
//
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
type StringStack struct {
|
|
||||||
list []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStringStack() *StringStack {
|
|
||||||
a := &StringStack{}
|
|
||||||
a.list = make([]string, 0)
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *StringStack) IsEmpty() bool {
|
|
||||||
return len(a.list) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *StringStack) Pop() (x string) {
|
|
||||||
x, a.list = a.list[0], a.list[1:len(a.list)]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *StringStack) Push(x string) {
|
|
||||||
a.list = append(a.list, x)
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) 2015 The heketi Authors
|
|
||||||
//
|
|
||||||
// This file is licensed to you under your choice of the GNU Lesser
|
|
||||||
// General Public License, version 3 or any later version (LGPLv3 or
|
|
||||||
// later), or the GNU General Public License, version 2 (GPLv2), in all
|
|
||||||
// cases as published by the Free Software Foundation.
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
// From http://www.ashishbanerjee.com/home/go/go-generate-uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"github.com/lpabon/godbc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return a 16-byte uuid
|
|
||||||
func GenUUID() string {
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
n, err := rand.Read(uuid)
|
|
||||||
godbc.Check(n == len(uuid), n, len(uuid))
|
|
||||||
godbc.Check(err == nil, err)
|
|
||||||
|
|
||||||
return hex.EncodeToString(uuid)
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.1
|
|
||||||
- 1.2
|
|
||||||
- tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get github.com/stretchr/testify
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test
|
|
||||||
- go test -tags 'prod'
|
|
|
@ -1 +0,0 @@
|
||||||
lpabon@redhat.com
|
|
|
@ -1,201 +0,0 @@
|
||||||
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:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) 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
|
|
||||||
|
|
||||||
(d) 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 {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,21 +0,0 @@
|
||||||
[![Build Status](https://travis-ci.org/lpabon/godbc.svg?branch=master)](https://travis-ci.org/lpabon/godbc)
|
|
||||||
|
|
||||||
# godbc
|
|
||||||
|
|
||||||
Design by contract for Go
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
To install godbc, use `go get`:
|
|
||||||
|
|
||||||
go get github.com/lpabon/godbc
|
|
||||||
|
|
||||||
Import the `godbc` package into your code using this template:
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/lpabon/godbc"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
|
|
||||||
Documentation is available at https://godoc.org/github.com/lpabon/godbc
|
|
|
@ -1,146 +0,0 @@
|
||||||
//+build !prod
|
|
||||||
|
|
||||||
//
|
|
||||||
// Copyright (c) 2014 The godbc Authors
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// Design-by-Contract for Go
|
|
||||||
//
|
|
||||||
// Design by Contract is a programming methodology
|
|
||||||
// which binds the caller and the function called to a
|
|
||||||
// contract. The contract is represented using Hoare Triple:
|
|
||||||
// {P} C {Q}
|
|
||||||
// where {P} is the precondition before executing command C,
|
|
||||||
// and {Q} is the postcondition.
|
|
||||||
//
|
|
||||||
// See Also
|
|
||||||
//
|
|
||||||
// * http://en.wikipedia.org/wiki/Design_by_contract
|
|
||||||
// * http://en.wikipedia.org/wiki/Hoare_logic
|
|
||||||
// * http://dlang.org/dbc.html
|
|
||||||
//
|
|
||||||
// Usage
|
|
||||||
//
|
|
||||||
// Godbc is enabled by default, but can be disabled for production
|
|
||||||
// builds by using the tag 'prod' in builds and tests as follows:
|
|
||||||
// go build -tags 'prod'
|
|
||||||
// or
|
|
||||||
// go test -tags 'prod'
|
|
||||||
//
|
|
||||||
package godbc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InvariantSimpleTester is an interface which provides a receiver to
|
|
||||||
// test the object
|
|
||||||
type InvariantSimpleTester interface {
|
|
||||||
Invariant() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvariantTester is an interface which provides not only an Invariant(),
|
|
||||||
// but also a receiver to print the structure
|
|
||||||
type InvariantTester interface {
|
|
||||||
InvariantSimpleTester
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// dbc_panic prints to the screen information of the failure followed
|
|
||||||
// by a call to panic()
|
|
||||||
func dbc_panic(dbc_func_name string, b bool, message ...interface{}) {
|
|
||||||
if !b {
|
|
||||||
|
|
||||||
// Get caller information which is the caller
|
|
||||||
// of the caller of this function
|
|
||||||
pc, file, line, _ := runtime.Caller(2)
|
|
||||||
caller_func_info := runtime.FuncForPC(pc)
|
|
||||||
|
|
||||||
error_string := fmt.Sprintf("%s:\n\r\tfunc (%s) 0x%x\n\r\tFile %s:%d",
|
|
||||||
dbc_func_name,
|
|
||||||
caller_func_info.Name(),
|
|
||||||
pc,
|
|
||||||
file,
|
|
||||||
line)
|
|
||||||
|
|
||||||
if len(message) > 0 {
|
|
||||||
error_string += fmt.Sprintf("\n\r\tInfo: %+v", message)
|
|
||||||
}
|
|
||||||
err := errors.New(error_string)
|
|
||||||
|
|
||||||
// Finally panic
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Require checks that the preconditions are satisfied before
|
|
||||||
// executing the function
|
|
||||||
//
|
|
||||||
// Example Code
|
|
||||||
//
|
|
||||||
// func Divide(a, b int) int {
|
|
||||||
// godbc.Require(b != 0)
|
|
||||||
// return a/b
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func Require(b bool, message ...interface{}) {
|
|
||||||
dbc_panic("REQUIRE", b, message...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure checks the postconditions are satisfied before returning
|
|
||||||
// to the caller.
|
|
||||||
//
|
|
||||||
// Example Code
|
|
||||||
//
|
|
||||||
// type Data struct {
|
|
||||||
// a int
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (*d Data) Set(a int) {
|
|
||||||
// d.a = a
|
|
||||||
// godbc.Ensure(d.a == a)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func Ensure(b bool, message ...interface{}) {
|
|
||||||
dbc_panic("ENSURE", b, message...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check provides a simple assert
|
|
||||||
func Check(b bool, message ...interface{}) {
|
|
||||||
dbc_panic("CHECK", b, message...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvariantSimple calls the objects Invariant() receiver to test
|
|
||||||
// the object for correctness.
|
|
||||||
//
|
|
||||||
// The caller object must provide an object that supports the
|
|
||||||
// interface InvariantSimpleTester and does not need to provide
|
|
||||||
// a String() receiver
|
|
||||||
func InvariantSimple(obj InvariantSimpleTester, message ...interface{}) {
|
|
||||||
dbc_panic("INVARIANT", obj.Invariant(), message...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invariant calls the objects Invariant() receiver to test
|
|
||||||
// the object for correctness.
|
|
||||||
//
|
|
||||||
// The caller object must provide an object that supports the
|
|
||||||
// interface InvariantTester
|
|
||||||
//
|
|
||||||
// To see an example, please take a look at the godbc_test.go
|
|
||||||
func Invariant(obj InvariantTester, message ...interface{}) {
|
|
||||||
m := append(message, obj)
|
|
||||||
dbc_panic("INVARIANT", obj.Invariant(), m)
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
//+build prod
|
|
||||||
|
|
||||||
//
|
|
||||||
// Copyright (c) 2014 The godbc Authors
|
|
||||||
//
|
|
||||||
// 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 godbc
|
|
||||||
|
|
||||||
type InvariantSimpleTester interface {
|
|
||||||
Invariant() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type InvariantTester interface {
|
|
||||||
InvariantSimpleTester
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Require(b bool, message ...interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func Ensure(b bool, message ...interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func Check(b bool, message ...interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func InvariantSimple(obj InvariantSimpleTester, message ...interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func Invariant(obj InvariantTester, message ...interface{}) {
|
|
||||||
}
|
|
Loading…
Reference in New Issue