mirror of
https://github.com/kubernetes/client-go.git
synced 2026-05-15 11:43:33 +00:00
Compare commits
72 Commits
v0.26.6
...
release-9.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
386e588352 | ||
|
|
b6aa6aafe3 | ||
|
|
d9857e2e80 | ||
|
|
8ca6fa75ef | ||
|
|
90c3c9898f | ||
|
|
57fa10648d | ||
|
|
77e032213d | ||
|
|
f8885b25ae | ||
|
|
701b913670 | ||
|
|
76bdf5a9dd | ||
|
|
832c4d8903 | ||
|
|
7bee181585 | ||
|
|
8d99f9c3d9 | ||
|
|
8ae8e474b0 | ||
|
|
5e6a3d4e34 | ||
|
|
f1cd4ed075 | ||
|
|
02f343fb27 | ||
|
|
f9c22737cd | ||
|
|
bf41d317a1 | ||
|
|
13596e875a | ||
|
|
3e32c83330 | ||
|
|
2126806484 | ||
|
|
c99ff00c5d | ||
|
|
073aba60c8 | ||
|
|
d914ce8e0a | ||
|
|
5b1220ffef | ||
|
|
782e840778 | ||
|
|
8f44824f9b | ||
|
|
1fb328fb33 | ||
|
|
539ab164ee | ||
|
|
b4527731f1 | ||
|
|
c268c175b4 | ||
|
|
173ad5fde8 | ||
|
|
227cb5081d | ||
|
|
1bb88b0329 | ||
|
|
65879682f7 | ||
|
|
cb4babb1f6 | ||
|
|
239503d989 | ||
|
|
ffd2ca39e7 | ||
|
|
d38fdf805c | ||
|
|
4a67cd79ce | ||
|
|
9eab462eec | ||
|
|
595a92811c | ||
|
|
7dc224ff75 | ||
|
|
96da731991 | ||
|
|
f272f4e32d | ||
|
|
4b8eacabe1 | ||
|
|
48fd11139e | ||
|
|
7e091e3663 | ||
|
|
e31aaf5592 | ||
|
|
ab58715064 | ||
|
|
5a488b5950 | ||
|
|
12e1fc4881 | ||
|
|
d3e82fd896 | ||
|
|
a11c470ac7 | ||
|
|
0c4a616cc6 | ||
|
|
5c58a85d6f | ||
|
|
d100788af7 | ||
|
|
32e2011330 | ||
|
|
2c1efc9ef4 | ||
|
|
f50e19ec6e | ||
|
|
7f2b32bee9 | ||
|
|
4026bdeec2 | ||
|
|
e46b34c539 | ||
|
|
5c69d94a91 | ||
|
|
32ec4b1882 | ||
|
|
cfba9881e4 | ||
|
|
50b95c82d9 | ||
|
|
ae46060c39 | ||
|
|
4927d9fd2d | ||
|
|
73fcd97c3a | ||
|
|
2cab959d03 |
@@ -3,6 +3,6 @@ language: go
|
||||
go_import_path: k8s.io/client-go
|
||||
|
||||
go:
|
||||
- 1.8.1
|
||||
- 1.10.2
|
||||
|
||||
script: go build ./...
|
||||
|
||||
184
Godeps/Godeps.json
generated
184
Godeps/Godeps.json
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ImportPath": "k8s.io/client-go",
|
||||
"GoVersion": "go1.10",
|
||||
"GodepVersion": "v80",
|
||||
"GodepVersion": "v80-k8s-r1",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/imdario/mergo",
|
||||
"Rev": "6633656539c1639d9d78127b7d47c622b5d7b6dc"
|
||||
"Rev": "9316a62528ac99aaecb4e47eadd6dc8aa6533d58"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/json-iterator/go",
|
||||
@@ -192,27 +192,27 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
||||
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context/ctxhttp",
|
||||
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
||||
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/http2",
|
||||
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
||||
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/http2/hpack",
|
||||
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
||||
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/idna",
|
||||
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
||||
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/lex/httplex",
|
||||
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
||||
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
@@ -272,331 +272,335 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/admissionregistration/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/admissionregistration/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/apps/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/apps/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/apps/v1beta2",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/authentication/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/authentication/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/authorization/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/authorization/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/autoscaling/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/autoscaling/v2beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/autoscaling/v2beta2",
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/batch/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/batch/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/batch/v2alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/certificates/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/coordination/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/core/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/events/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/extensions/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/imagepolicy/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/networking/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/policy/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/rbac/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/rbac/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/rbac/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/scheduling/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/scheduling/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/settings/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/storage/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/storage/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/storage/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "6db15a15d2d3874a6c3ddb2140ac9f3bc7058428"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/fuzzer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/roundtrip",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/fields",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/labels",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/selection",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/types",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/naming",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/version",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/watch",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||
|
||||
@@ -63,7 +63,7 @@ To install `client-go` and place its dependencies in your `$GOPATH`:
|
||||
```sh
|
||||
go get k8s.io/client-go/...
|
||||
cd $GOPATH/src/k8s.io/client-go
|
||||
git checkout v6.0.0
|
||||
git checkout v9.0.0 # replace v9.0.0 with the required version
|
||||
# cd 1.5 # only necessary with 1.5 and 1.4 clients.
|
||||
godep restore ./...
|
||||
```
|
||||
@@ -99,7 +99,7 @@ your project:
|
||||
package: ( your project's import path ) # e.g. github.com/foo/bar
|
||||
import:
|
||||
- package: k8s.io/client-go
|
||||
version: v6.0.0
|
||||
version: v9.0.0 # replace v9.0.0 with the required version
|
||||
```
|
||||
|
||||
Second, add a Go file that imports `client-go` somewhere in your project,
|
||||
@@ -132,7 +132,7 @@ requests can override the version manually in `glide.yaml`. For example:
|
||||
package: ( your project's import path ) # e.g. github.com/foo/bar
|
||||
import:
|
||||
- package: k8s.io/client-go
|
||||
version: v6.0.0
|
||||
version: v9.0.0 # replace v9.0.0 with the required version
|
||||
# Use a newer version of go-spew even though client-go wants an old one.
|
||||
- package: github.com/davecgh/go-spew
|
||||
version: v1.1.0
|
||||
|
||||
2
OWNERS
2
OWNERS
@@ -13,3 +13,5 @@ reviewers:
|
||||
- soltysh
|
||||
- sttts
|
||||
- yliaog
|
||||
labels:
|
||||
- sig/api-machinery
|
||||
|
||||
@@ -162,7 +162,7 @@ func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Object) error {
|
||||
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(filename), 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Obj
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Chmod(f.Name(), 0755)
|
||||
err = os.Chmod(f.Name(), 0660)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package discovery
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -95,6 +96,32 @@ func TestNewCachedDiscoveryClient_TTL(t *testing.T) {
|
||||
assert.Equal(c.groupCalls, 2)
|
||||
}
|
||||
|
||||
func TestNewCachedDiscoveryClient_PathPerm(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
d, err := ioutil.TempDir("", "")
|
||||
assert.NoError(err)
|
||||
os.RemoveAll(d)
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
c := fakeDiscoveryClient{}
|
||||
cdc := newCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
|
||||
cdc.ServerGroups()
|
||||
|
||||
err = filepath.Walk(d, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
assert.Equal(os.FileMode(0750), info.Mode().Perm())
|
||||
} else {
|
||||
assert.Equal(os.FileMode(0660), info.Mode().Perm())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
type fakeDiscoveryClient struct {
|
||||
groupCalls int
|
||||
resourceCalls int
|
||||
|
||||
@@ -18,6 +18,7 @@ package discovery
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang/glog"
|
||||
@@ -35,6 +36,8 @@ type cacheRoundTripper struct {
|
||||
// corresponding requests.
|
||||
func newCacheRoundTripper(cacheDir string, rt http.RoundTripper) http.RoundTripper {
|
||||
d := diskv.New(diskv.Options{
|
||||
PathPerm: os.FileMode(0750),
|
||||
FilePerm: os.FileMode(0660),
|
||||
BasePath: cacheDir,
|
||||
TempDir: filepath.Join(cacheDir, ".diskv-temp"),
|
||||
})
|
||||
|
||||
@@ -22,7 +22,10 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// copied from k8s.io/client-go/transport/round_trippers_test.go
|
||||
@@ -93,3 +96,52 @@ func TestCacheRoundTripper(t *testing.T) {
|
||||
t.Errorf("Invalid content read from cache %q", string(content))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheRoundTripperPathPerm(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
rt := &testRoundTripper{}
|
||||
cacheDir, err := ioutil.TempDir("", "cache-rt")
|
||||
os.RemoveAll(cacheDir)
|
||||
defer os.RemoveAll(cacheDir)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cache := newCacheRoundTripper(cacheDir, rt)
|
||||
|
||||
// First call, caches the response
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: &url.URL{Host: "localhost"},
|
||||
}
|
||||
rt.Response = &http.Response{
|
||||
Header: http.Header{"ETag": []string{`"123456"`}},
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte("Content"))),
|
||||
StatusCode: http.StatusOK,
|
||||
}
|
||||
resp, err := cache.RoundTrip(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(content) != "Content" {
|
||||
t.Errorf(`Expected Body to be "Content", got %q`, string(content))
|
||||
}
|
||||
|
||||
err = filepath.Walk(cacheDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
assert.Equal(os.FileMode(0750), info.Mode().Perm())
|
||||
} else {
|
||||
assert.Equal(os.FileMode(0660), info.Mode().Perm())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
@@ -303,6 +303,7 @@ func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.Uns
|
||||
}
|
||||
|
||||
list := &unstructured.UnstructuredList{}
|
||||
list.SetResourceVersion(entireList.GetResourceVersion())
|
||||
for i := range entireList.Items {
|
||||
item := &entireList.Items[i]
|
||||
metadata, err := meta.Accessor(item)
|
||||
|
||||
@@ -21,6 +21,7 @@ package autoscaling
|
||||
import (
|
||||
v1 "k8s.io/client-go/informers/autoscaling/v1"
|
||||
v2beta1 "k8s.io/client-go/informers/autoscaling/v2beta1"
|
||||
v2beta2 "k8s.io/client-go/informers/autoscaling/v2beta2"
|
||||
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
|
||||
)
|
||||
|
||||
@@ -30,6 +31,8 @@ type Interface interface {
|
||||
V1() v1.Interface
|
||||
// V2beta1 provides access to shared informers for resources in V2beta1.
|
||||
V2beta1() v2beta1.Interface
|
||||
// V2beta2 provides access to shared informers for resources in V2beta2.
|
||||
V2beta2() v2beta2.Interface
|
||||
}
|
||||
|
||||
type group struct {
|
||||
@@ -52,3 +55,8 @@ func (g *group) V1() v1.Interface {
|
||||
func (g *group) V2beta1() v2beta1.Interface {
|
||||
return v2beta1.New(g.factory, g.namespace, g.tweakListOptions)
|
||||
}
|
||||
|
||||
// V2beta2 returns a new v2beta2.Interface.
|
||||
func (g *group) V2beta2() v2beta2.Interface {
|
||||
return v2beta2.New(g.factory, g.namespace, g.tweakListOptions)
|
||||
}
|
||||
|
||||
89
informers/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
89
informers/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
|
||||
kubernetes "k8s.io/client-go/kubernetes"
|
||||
v2beta2 "k8s.io/client-go/listers/autoscaling/v2beta2"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// HorizontalPodAutoscalerInformer provides access to a shared informer and lister for
|
||||
// HorizontalPodAutoscalers.
|
||||
type HorizontalPodAutoscalerInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() v2beta2.HorizontalPodAutoscalerLister
|
||||
}
|
||||
|
||||
type horizontalPodAutoscalerInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredHorizontalPodAutoscalerInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFilteredHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).List(options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).Watch(options)
|
||||
},
|
||||
},
|
||||
&autoscalingv2beta2.HorizontalPodAutoscaler{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *horizontalPodAutoscalerInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredHorizontalPodAutoscalerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *horizontalPodAutoscalerInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&autoscalingv2beta2.HorizontalPodAutoscaler{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *horizontalPodAutoscalerInformer) Lister() v2beta2.HorizontalPodAutoscalerLister {
|
||||
return v2beta2.NewHorizontalPodAutoscalerLister(f.Informer().GetIndexer())
|
||||
}
|
||||
45
informers/autoscaling/v2beta2/interface.go
Normal file
45
informers/autoscaling/v2beta2/interface.go
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
import (
|
||||
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
|
||||
)
|
||||
|
||||
// Interface provides access to all the informers in this group version.
|
||||
type Interface interface {
|
||||
// HorizontalPodAutoscalers returns a HorizontalPodAutoscalerInformer.
|
||||
HorizontalPodAutoscalers() HorizontalPodAutoscalerInformer
|
||||
}
|
||||
|
||||
type version struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// HorizontalPodAutoscalers returns a HorizontalPodAutoscalerInformer.
|
||||
func (v *version) HorizontalPodAutoscalers() HorizontalPodAutoscalerInformer {
|
||||
return &horizontalPodAutoscalerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
v1beta2 "k8s.io/api/apps/v1beta2"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
v2beta1 "k8s.io/api/autoscaling/v2beta1"
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
v2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
@@ -127,6 +128,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
|
||||
case v2beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().V2beta1().HorizontalPodAutoscalers().Informer()}, nil
|
||||
|
||||
// Group=autoscaling, Version=v2beta2
|
||||
case v2beta2.SchemeGroupVersion.WithResource("horizontalpodautoscalers"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().V2beta2().HorizontalPodAutoscalers().Informer()}, nil
|
||||
|
||||
// Group=batch, Version=v1
|
||||
case batchv1.SchemeGroupVersion.WithResource("jobs"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Batch().V1().Jobs().Informer()}, nil
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
authorizationv1beta1 "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
|
||||
autoscalingv1 "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
|
||||
autoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1"
|
||||
autoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2"
|
||||
batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
|
||||
batchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
|
||||
batchv2alpha1 "k8s.io/client-go/kubernetes/typed/batch/v2alpha1"
|
||||
@@ -77,6 +78,7 @@ type Interface interface {
|
||||
// Deprecated: please explicitly pick a version if possible.
|
||||
Autoscaling() autoscalingv1.AutoscalingV1Interface
|
||||
AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface
|
||||
AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface
|
||||
BatchV1() batchv1.BatchV1Interface
|
||||
// Deprecated: please explicitly pick a version if possible.
|
||||
Batch() batchv1.BatchV1Interface
|
||||
@@ -137,6 +139,7 @@ type Clientset struct {
|
||||
authorizationV1beta1 *authorizationv1beta1.AuthorizationV1beta1Client
|
||||
autoscalingV1 *autoscalingv1.AutoscalingV1Client
|
||||
autoscalingV2beta1 *autoscalingv2beta1.AutoscalingV2beta1Client
|
||||
autoscalingV2beta2 *autoscalingv2beta2.AutoscalingV2beta2Client
|
||||
batchV1 *batchv1.BatchV1Client
|
||||
batchV1beta1 *batchv1beta1.BatchV1beta1Client
|
||||
batchV2alpha1 *batchv2alpha1.BatchV2alpha1Client
|
||||
@@ -243,6 +246,11 @@ func (c *Clientset) AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1In
|
||||
return c.autoscalingV2beta1
|
||||
}
|
||||
|
||||
// AutoscalingV2beta2 retrieves the AutoscalingV2beta2Client
|
||||
func (c *Clientset) AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface {
|
||||
return c.autoscalingV2beta2
|
||||
}
|
||||
|
||||
// BatchV1 retrieves the BatchV1Client
|
||||
func (c *Clientset) BatchV1() batchv1.BatchV1Interface {
|
||||
return c.batchV1
|
||||
@@ -470,6 +478,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.autoscalingV2beta2, err = autoscalingv2beta2.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.batchV1, err = batchv1.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -569,6 +581,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
cs.authorizationV1beta1 = authorizationv1beta1.NewForConfigOrDie(c)
|
||||
cs.autoscalingV1 = autoscalingv1.NewForConfigOrDie(c)
|
||||
cs.autoscalingV2beta1 = autoscalingv2beta1.NewForConfigOrDie(c)
|
||||
cs.autoscalingV2beta2 = autoscalingv2beta2.NewForConfigOrDie(c)
|
||||
cs.batchV1 = batchv1.NewForConfigOrDie(c)
|
||||
cs.batchV1beta1 = batchv1beta1.NewForConfigOrDie(c)
|
||||
cs.batchV2alpha1 = batchv2alpha1.NewForConfigOrDie(c)
|
||||
@@ -607,6 +620,7 @@ func New(c rest.Interface) *Clientset {
|
||||
cs.authorizationV1beta1 = authorizationv1beta1.New(c)
|
||||
cs.autoscalingV1 = autoscalingv1.New(c)
|
||||
cs.autoscalingV2beta1 = autoscalingv2beta1.New(c)
|
||||
cs.autoscalingV2beta2 = autoscalingv2beta2.New(c)
|
||||
cs.batchV1 = batchv1.New(c)
|
||||
cs.batchV1beta1 = batchv1beta1.New(c)
|
||||
cs.batchV2alpha1 = batchv2alpha1.New(c)
|
||||
|
||||
@@ -46,6 +46,8 @@ import (
|
||||
fakeautoscalingv1 "k8s.io/client-go/kubernetes/typed/autoscaling/v1/fake"
|
||||
autoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1"
|
||||
fakeautoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake"
|
||||
autoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2"
|
||||
fakeautoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake"
|
||||
batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
|
||||
fakebatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1/fake"
|
||||
batchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
|
||||
@@ -209,6 +211,11 @@ func (c *Clientset) AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1In
|
||||
return &fakeautoscalingv2beta1.FakeAutoscalingV2beta1{Fake: &c.Fake}
|
||||
}
|
||||
|
||||
// AutoscalingV2beta2 retrieves the AutoscalingV2beta2Client
|
||||
func (c *Clientset) AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface {
|
||||
return &fakeautoscalingv2beta2.FakeAutoscalingV2beta2{Fake: &c.Fake}
|
||||
}
|
||||
|
||||
// BatchV1 retrieves the BatchV1Client
|
||||
func (c *Clientset) BatchV1() batchv1.BatchV1Interface {
|
||||
return &fakebatchv1.FakeBatchV1{Fake: &c.Fake}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
|
||||
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
@@ -71,6 +72,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
authorizationv1beta1.AddToScheme,
|
||||
autoscalingv1.AddToScheme,
|
||||
autoscalingv2beta1.AddToScheme,
|
||||
autoscalingv2beta2.AddToScheme,
|
||||
batchv1.AddToScheme,
|
||||
batchv1beta1.AddToScheme,
|
||||
batchv2alpha1.AddToScheme,
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
|
||||
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
@@ -71,6 +72,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
authorizationv1beta1.AddToScheme,
|
||||
autoscalingv1.AddToScheme,
|
||||
autoscalingv2beta1.AddToScheme,
|
||||
autoscalingv2beta2.AddToScheme,
|
||||
batchv1.AddToScheme,
|
||||
batchv1beta1.AddToScheme,
|
||||
batchv2alpha1.AddToScheme,
|
||||
|
||||
90
kubernetes/typed/autoscaling/v2beta2/autoscaling_client.go
Normal file
90
kubernetes/typed/autoscaling/v2beta2/autoscaling_client.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
import (
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type AutoscalingV2beta2Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
HorizontalPodAutoscalersGetter
|
||||
}
|
||||
|
||||
// AutoscalingV2beta2Client is used to interact with features provided by the autoscaling group.
|
||||
type AutoscalingV2beta2Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *AutoscalingV2beta2Client) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface {
|
||||
return newHorizontalPodAutoscalers(c, namespace)
|
||||
}
|
||||
|
||||
// NewForConfig creates a new AutoscalingV2beta2Client for the given config.
|
||||
func NewForConfig(c *rest.Config) (*AutoscalingV2beta2Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientFor(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AutoscalingV2beta2Client{client}, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new AutoscalingV2beta2Client for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *AutoscalingV2beta2Client {
|
||||
client, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// New creates a new AutoscalingV2beta2Client for the given RESTClient.
|
||||
func New(c rest.Interface) *AutoscalingV2beta2Client {
|
||||
return &AutoscalingV2beta2Client{c}
|
||||
}
|
||||
|
||||
func setConfigDefaults(config *rest.Config) error {
|
||||
gv := v2beta2.SchemeGroupVersion
|
||||
config.GroupVersion = &gv
|
||||
config.APIPath = "/apis"
|
||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
|
||||
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *AutoscalingV2beta2Client) RESTClient() rest.Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.restClient
|
||||
}
|
||||
20
kubernetes/typed/autoscaling/v2beta2/doc.go
Normal file
20
kubernetes/typed/autoscaling/v2beta2/doc.go
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated typed clients.
|
||||
package v2beta2
|
||||
20
kubernetes/typed/autoscaling/v2beta2/fake/doc.go
Normal file
20
kubernetes/typed/autoscaling/v2beta2/fake/doc.go
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// Package fake has the automatically generated clients.
|
||||
package fake
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
v2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2"
|
||||
rest "k8s.io/client-go/rest"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
type FakeAutoscalingV2beta2 struct {
|
||||
*testing.Fake
|
||||
}
|
||||
|
||||
func (c *FakeAutoscalingV2beta2) HorizontalPodAutoscalers(namespace string) v2beta2.HorizontalPodAutoscalerInterface {
|
||||
return &FakeHorizontalPodAutoscalers{c, namespace}
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *FakeAutoscalingV2beta2) RESTClient() rest.Interface {
|
||||
var ret *rest.RESTClient
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface
|
||||
type FakeHorizontalPodAutoscalers struct {
|
||||
Fake *FakeAutoscalingV2beta2
|
||||
ns string
|
||||
}
|
||||
|
||||
var horizontalpodautoscalersResource = schema.GroupVersionResource{Group: "autoscaling", Version: "v2beta2", Resource: "horizontalpodautoscalers"}
|
||||
|
||||
var horizontalpodautoscalersKind = schema.GroupVersionKind{Group: "autoscaling", Version: "v2beta2", Kind: "HorizontalPodAutoscaler"}
|
||||
|
||||
// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any.
|
||||
func (c *FakeHorizontalPodAutoscalers) Get(name string, options v1.GetOptions) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewGetAction(horizontalpodautoscalersResource, c.ns, name), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors.
|
||||
func (c *FakeHorizontalPodAutoscalers) List(opts v1.ListOptions) (result *v2beta2.HorizontalPodAutoscalerList, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewListAction(horizontalpodautoscalersResource, horizontalpodautoscalersKind, c.ns, opts), &v2beta2.HorizontalPodAutoscalerList{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||
if label == nil {
|
||||
label = labels.Everything()
|
||||
}
|
||||
list := &v2beta2.HorizontalPodAutoscalerList{ListMeta: obj.(*v2beta2.HorizontalPodAutoscalerList).ListMeta}
|
||||
for _, item := range obj.(*v2beta2.HorizontalPodAutoscalerList).Items {
|
||||
if label.Matches(labels.Set(item.Labels)) {
|
||||
list.Items = append(list.Items, item)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers.
|
||||
func (c *FakeHorizontalPodAutoscalers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
return c.Fake.
|
||||
InvokesWatch(testing.NewWatchAction(horizontalpodautoscalersResource, c.ns, opts))
|
||||
|
||||
}
|
||||
|
||||
// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any.
|
||||
func (c *FakeHorizontalPodAutoscalers) Create(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewCreateAction(horizontalpodautoscalersResource, c.ns, horizontalPodAutoscaler), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
|
||||
// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any.
|
||||
func (c *FakeHorizontalPodAutoscalers) Update(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateAction(horizontalpodautoscalersResource, c.ns, horizontalPodAutoscaler), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *FakeHorizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (*v2beta2.HorizontalPodAutoscaler, error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateSubresourceAction(horizontalpodautoscalersResource, "status", c.ns, horizontalPodAutoscaler), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
|
||||
// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeHorizontalPodAutoscalers) Delete(name string, options *v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(horizontalpodautoscalersResource, c.ns, name), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *FakeHorizontalPodAutoscalers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
action := testing.NewDeleteCollectionAction(horizontalpodautoscalersResource, c.ns, listOptions)
|
||||
|
||||
_, err := c.Fake.Invokes(action, &v2beta2.HorizontalPodAutoscalerList{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched horizontalPodAutoscaler.
|
||||
func (c *FakeHorizontalPodAutoscalers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(horizontalpodautoscalersResource, c.ns, name, data, subresources...), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
Copyright 2011-2016 Canonical Ltd.
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -11,3 +12,10 @@ 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.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
type HorizontalPodAutoscalerExpansion interface{}
|
||||
174
kubernetes/typed/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
174
kubernetes/typed/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
import (
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
scheme "k8s.io/client-go/kubernetes/scheme"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// HorizontalPodAutoscalersGetter has a method to return a HorizontalPodAutoscalerInterface.
|
||||
// A group's client should implement this interface.
|
||||
type HorizontalPodAutoscalersGetter interface {
|
||||
HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface
|
||||
}
|
||||
|
||||
// HorizontalPodAutoscalerInterface has methods to work with HorizontalPodAutoscaler resources.
|
||||
type HorizontalPodAutoscalerInterface interface {
|
||||
Create(*v2beta2.HorizontalPodAutoscaler) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
Update(*v2beta2.HorizontalPodAutoscaler) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
UpdateStatus(*v2beta2.HorizontalPodAutoscaler) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
Delete(name string, options *v1.DeleteOptions) error
|
||||
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
|
||||
Get(name string, options v1.GetOptions) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
List(opts v1.ListOptions) (*v2beta2.HorizontalPodAutoscalerList, error)
|
||||
Watch(opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v2beta2.HorizontalPodAutoscaler, err error)
|
||||
HorizontalPodAutoscalerExpansion
|
||||
}
|
||||
|
||||
// horizontalPodAutoscalers implements HorizontalPodAutoscalerInterface
|
||||
type horizontalPodAutoscalers struct {
|
||||
client rest.Interface
|
||||
ns string
|
||||
}
|
||||
|
||||
// newHorizontalPodAutoscalers returns a HorizontalPodAutoscalers
|
||||
func newHorizontalPodAutoscalers(c *AutoscalingV2beta2Client, namespace string) *horizontalPodAutoscalers {
|
||||
return &horizontalPodAutoscalers{
|
||||
client: c.RESTClient(),
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any.
|
||||
func (c *horizontalPodAutoscalers) Get(name string, options v1.GetOptions) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors.
|
||||
func (c *horizontalPodAutoscalers) List(opts v1.ListOptions) (result *v2beta2.HorizontalPodAutoscalerList, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscalerList{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers.
|
||||
func (c *horizontalPodAutoscalers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
opts.Watch = true
|
||||
return c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any.
|
||||
func (c *horizontalPodAutoscalers) Create(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Post().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Body(horizontalPodAutoscaler).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any.
|
||||
func (c *horizontalPodAutoscalers) Update(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Name(horizontalPodAutoscaler.Name).
|
||||
Body(horizontalPodAutoscaler).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
|
||||
func (c *horizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Name(horizontalPodAutoscaler.Name).
|
||||
SubResource("status").
|
||||
Body(horizontalPodAutoscaler).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs.
|
||||
func (c *horizontalPodAutoscalers) Delete(name string, options *v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *horizontalPodAutoscalers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
VersionedParams(&listOptions, scheme.ParameterCodec).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched horizontalPodAutoscaler.
|
||||
func (c *horizontalPodAutoscalers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Patch(pt).
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
SubResource(subresources...).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
27
listers/autoscaling/v2beta2/expansion_generated.go
Normal file
27
listers/autoscaling/v2beta2/expansion_generated.go
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
// HorizontalPodAutoscalerListerExpansion allows custom methods to be added to
|
||||
// HorizontalPodAutoscalerLister.
|
||||
type HorizontalPodAutoscalerListerExpansion interface{}
|
||||
|
||||
// HorizontalPodAutoscalerNamespaceListerExpansion allows custom methods to be added to
|
||||
// HorizontalPodAutoscalerNamespaceLister.
|
||||
type HorizontalPodAutoscalerNamespaceListerExpansion interface{}
|
||||
94
listers/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
94
listers/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
import (
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// HorizontalPodAutoscalerLister helps list HorizontalPodAutoscalers.
|
||||
type HorizontalPodAutoscalerLister interface {
|
||||
// List lists all HorizontalPodAutoscalers in the indexer.
|
||||
List(selector labels.Selector) (ret []*v2beta2.HorizontalPodAutoscaler, err error)
|
||||
// HorizontalPodAutoscalers returns an object that can list and get HorizontalPodAutoscalers.
|
||||
HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerNamespaceLister
|
||||
HorizontalPodAutoscalerListerExpansion
|
||||
}
|
||||
|
||||
// horizontalPodAutoscalerLister implements the HorizontalPodAutoscalerLister interface.
|
||||
type horizontalPodAutoscalerLister struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
// NewHorizontalPodAutoscalerLister returns a new HorizontalPodAutoscalerLister.
|
||||
func NewHorizontalPodAutoscalerLister(indexer cache.Indexer) HorizontalPodAutoscalerLister {
|
||||
return &horizontalPodAutoscalerLister{indexer: indexer}
|
||||
}
|
||||
|
||||
// List lists all HorizontalPodAutoscalers in the indexer.
|
||||
func (s *horizontalPodAutoscalerLister) List(selector labels.Selector) (ret []*v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v2beta2.HorizontalPodAutoscaler))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// HorizontalPodAutoscalers returns an object that can list and get HorizontalPodAutoscalers.
|
||||
func (s *horizontalPodAutoscalerLister) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerNamespaceLister {
|
||||
return horizontalPodAutoscalerNamespaceLister{indexer: s.indexer, namespace: namespace}
|
||||
}
|
||||
|
||||
// HorizontalPodAutoscalerNamespaceLister helps list and get HorizontalPodAutoscalers.
|
||||
type HorizontalPodAutoscalerNamespaceLister interface {
|
||||
// List lists all HorizontalPodAutoscalers in the indexer for a given namespace.
|
||||
List(selector labels.Selector) (ret []*v2beta2.HorizontalPodAutoscaler, err error)
|
||||
// Get retrieves the HorizontalPodAutoscaler from the indexer for a given namespace and name.
|
||||
Get(name string) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
HorizontalPodAutoscalerNamespaceListerExpansion
|
||||
}
|
||||
|
||||
// horizontalPodAutoscalerNamespaceLister implements the HorizontalPodAutoscalerNamespaceLister
|
||||
// interface.
|
||||
type horizontalPodAutoscalerNamespaceLister struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
}
|
||||
|
||||
// List lists all HorizontalPodAutoscalers in the indexer for a given namespace.
|
||||
func (s horizontalPodAutoscalerNamespaceLister) List(selector labels.Selector) (ret []*v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v2beta2.HorizontalPodAutoscaler))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get retrieves the HorizontalPodAutoscaler from the indexer for a given namespace and name.
|
||||
func (s horizontalPodAutoscalerNamespaceLister) Get(name string) (*v2beta2.HorizontalPodAutoscaler, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(v2beta2.Resource("horizontalpodautoscaler"), name)
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), nil
|
||||
}
|
||||
@@ -174,7 +174,13 @@ func parseScopes(gcpConfig map[string]string) []string {
|
||||
}
|
||||
|
||||
func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
|
||||
return &conditionalTransport{&oauth2.Transport{Source: g.tokenSource, Base: rt}, g.persister}
|
||||
var resetCache map[string]string
|
||||
if cts, ok := g.tokenSource.(*cachedTokenSource); ok {
|
||||
resetCache = cts.baseCache()
|
||||
} else {
|
||||
resetCache = make(map[string]string)
|
||||
}
|
||||
return &conditionalTransport{&oauth2.Transport{Source: g.tokenSource, Base: rt}, g.persister, resetCache}
|
||||
}
|
||||
|
||||
func (g *gcpAuthProvider) Login() error { return nil }
|
||||
@@ -247,6 +253,19 @@ func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string {
|
||||
return ret
|
||||
}
|
||||
|
||||
// baseCache is the base configuration value for this TokenSource, without any cached ephemeral tokens.
|
||||
func (t *cachedTokenSource) baseCache() map[string]string {
|
||||
t.lk.Lock()
|
||||
defer t.lk.Unlock()
|
||||
ret := map[string]string{}
|
||||
for k, v := range t.cache {
|
||||
ret[k] = v
|
||||
}
|
||||
delete(ret, "access-token")
|
||||
delete(ret, "expiry")
|
||||
return ret
|
||||
}
|
||||
|
||||
type commandTokenSource struct {
|
||||
cmd string
|
||||
args []string
|
||||
@@ -337,6 +356,7 @@ func parseJSONPath(input interface{}, name, template string) (string, error) {
|
||||
type conditionalTransport struct {
|
||||
oauthTransport *oauth2.Transport
|
||||
persister restclient.AuthProviderConfigPersister
|
||||
resetCache map[string]string
|
||||
}
|
||||
|
||||
var _ net.RoundTripperWrapper = &conditionalTransport{}
|
||||
@@ -354,8 +374,7 @@ func (t *conditionalTransport) RoundTrip(req *http.Request) (*http.Response, err
|
||||
|
||||
if res.StatusCode == 401 {
|
||||
glog.V(4).Infof("The credentials that were supplied are invalid for the target cluster")
|
||||
emptyCache := make(map[string]string)
|
||||
t.persister.Persist(emptyCache)
|
||||
t.persister.Persist(t.resetCache)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
||||
@@ -442,37 +442,61 @@ func (t *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return t.res, nil
|
||||
}
|
||||
|
||||
func TestClearingCredentials(t *testing.T) {
|
||||
func Test_cmdTokenSource_roundTrip(t *testing.T) {
|
||||
|
||||
accessToken := "fakeToken"
|
||||
fakeExpiry := time.Now().Add(time.Hour)
|
||||
|
||||
cache := map[string]string{
|
||||
"access-token": "fakeToken",
|
||||
"expiry": fakeExpiry.String(),
|
||||
fakeExpiryStr := fakeExpiry.Format(time.RFC3339Nano)
|
||||
fs := &fakeTokenSource{
|
||||
token: &oauth2.Token{
|
||||
AccessToken: accessToken,
|
||||
Expiry: fakeExpiry,
|
||||
},
|
||||
}
|
||||
|
||||
cts := cachedTokenSource{
|
||||
source: nil,
|
||||
accessToken: cache["access-token"],
|
||||
expiry: fakeExpiry,
|
||||
persister: nil,
|
||||
cache: nil,
|
||||
cmdCache := map[string]string{
|
||||
"cmd-path": "/path/to/tokensource/cmd",
|
||||
"cmd-args": "--output=json",
|
||||
}
|
||||
cmdCacheUpdated := map[string]string{
|
||||
"cmd-path": "/path/to/tokensource/cmd",
|
||||
"cmd-args": "--output=json",
|
||||
"access-token": accessToken,
|
||||
"expiry": fakeExpiryStr,
|
||||
}
|
||||
simpleCacheUpdated := map[string]string{
|
||||
"access-token": accessToken,
|
||||
"expiry": fakeExpiryStr,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
res http.Response
|
||||
cache map[string]string
|
||||
name string
|
||||
res http.Response
|
||||
baseCache, expectedCache map[string]string
|
||||
}{
|
||||
{
|
||||
"Unauthorized",
|
||||
http.Response{StatusCode: 401},
|
||||
make(map[string]string),
|
||||
make(map[string]string),
|
||||
},
|
||||
{
|
||||
"Unauthorized, nonempty defaultCache",
|
||||
http.Response{StatusCode: 401},
|
||||
cmdCache,
|
||||
cmdCache,
|
||||
},
|
||||
{
|
||||
"Authorized",
|
||||
http.Response{StatusCode: 200},
|
||||
cache,
|
||||
make(map[string]string),
|
||||
simpleCacheUpdated,
|
||||
},
|
||||
{
|
||||
"Authorized, nonempty defaultCache",
|
||||
http.Response{StatusCode: 200},
|
||||
cmdCache,
|
||||
cmdCacheUpdated,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -480,17 +504,23 @@ func TestClearingCredentials(t *testing.T) {
|
||||
req := http.Request{Header: http.Header{}}
|
||||
|
||||
for _, tc := range tests {
|
||||
authProvider := gcpAuthProvider{&cts, persister}
|
||||
cts, err := newCachedTokenSource(accessToken, fakeExpiry.String(), persister, fs, tc.baseCache)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from newCachedTokenSource: %v", err)
|
||||
}
|
||||
authProvider := gcpAuthProvider{cts, persister}
|
||||
|
||||
fakeTransport := MockTransport{&tc.res}
|
||||
|
||||
transport := (authProvider.WrapTransport(&fakeTransport))
|
||||
persister.Persist(cache)
|
||||
// call Token to persist/update cache
|
||||
if _, err := cts.Token(); err != nil {
|
||||
t.Fatalf("unexpected error from cachedTokenSource.Token(): %v", err)
|
||||
}
|
||||
|
||||
transport.RoundTrip(&req)
|
||||
|
||||
if got := persister.read(); !reflect.DeepEqual(got, tc.cache) {
|
||||
t.Errorf("got cache %v, want %v", got, tc.cache)
|
||||
if got := persister.read(); !reflect.DeepEqual(got, tc.expectedCache) {
|
||||
t.Errorf("got cache %v, want %v", got, tc.expectedCache)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -29,7 +30,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -44,6 +44,8 @@ const (
|
||||
DefaultBurst int = 10
|
||||
)
|
||||
|
||||
var ErrNotInCluster = errors.New("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")
|
||||
|
||||
// Config holds the common attributes that can be passed to a Kubernetes client on
|
||||
// initialization.
|
||||
type Config struct {
|
||||
@@ -68,6 +70,11 @@ type Config struct {
|
||||
// TODO: demonstrate an OAuth2 compatible client.
|
||||
BearerToken string
|
||||
|
||||
// Path to a file containing a BearerToken.
|
||||
// If set, the contents are periodically read.
|
||||
// The last successfully read value takes precedence over BearerToken.
|
||||
BearerTokenFile string
|
||||
|
||||
// Impersonate is the configuration that RESTClient will use for impersonation.
|
||||
Impersonate ImpersonationConfig
|
||||
|
||||
@@ -308,20 +315,25 @@ func DefaultKubernetesUserAgent() string {
|
||||
|
||||
// InClusterConfig returns a config object which uses the service account
|
||||
// kubernetes gives to pods. It's intended for clients that expect to be
|
||||
// running inside a pod running on kubernetes. It will return an error if
|
||||
// called from a process not running in a kubernetes environment.
|
||||
// running inside a pod running on kubernetes. It will return ErrNotInCluster
|
||||
// if called from a process not running in a kubernetes environment.
|
||||
func InClusterConfig() (*Config, error) {
|
||||
const (
|
||||
tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
)
|
||||
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
|
||||
if len(host) == 0 || len(port) == 0 {
|
||||
return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")
|
||||
return nil, ErrNotInCluster
|
||||
}
|
||||
|
||||
token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
|
||||
token, err := ioutil.ReadFile(tokenFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsClientConfig := TLSClientConfig{}
|
||||
rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
|
||||
if _, err := certutil.NewPool(rootCAFile); err != nil {
|
||||
glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
|
||||
} else {
|
||||
@@ -331,8 +343,9 @@ func InClusterConfig() (*Config, error) {
|
||||
return &Config{
|
||||
// TODO: switch to using cluster DNS.
|
||||
Host: "https://" + net.JoinHostPort(host, port),
|
||||
BearerToken: string(token),
|
||||
TLSClientConfig: tlsClientConfig,
|
||||
BearerToken: string(token),
|
||||
BearerTokenFile: tokenFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -422,12 +435,13 @@ func AnonymousClientConfig(config *Config) *Config {
|
||||
// CopyConfig returns a copy of the given config
|
||||
func CopyConfig(config *Config) *Config {
|
||||
return &Config{
|
||||
Host: config.Host,
|
||||
APIPath: config.APIPath,
|
||||
ContentConfig: config.ContentConfig,
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
BearerToken: config.BearerToken,
|
||||
Host: config.Host,
|
||||
APIPath: config.APIPath,
|
||||
ContentConfig: config.ContentConfig,
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
BearerToken: config.BearerToken,
|
||||
BearerTokenFile: config.BearerTokenFile,
|
||||
Impersonate: ImpersonationConfig{
|
||||
Groups: config.Impersonate.Groups,
|
||||
Extra: config.Impersonate.Extra,
|
||||
|
||||
@@ -264,6 +264,7 @@ func TestAnonymousConfig(t *testing.T) {
|
||||
// is added to Config, update AnonymousClientConfig to preserve the field otherwise.
|
||||
expected.Impersonate = ImpersonationConfig{}
|
||||
expected.BearerToken = ""
|
||||
expected.BearerTokenFile = ""
|
||||
expected.Username = ""
|
||||
expected.Password = ""
|
||||
expected.AuthProvider = nil
|
||||
|
||||
@@ -198,7 +198,7 @@ func (r *Request) Throttle(limiter flowcontrol.RateLimiter) *Request {
|
||||
return r
|
||||
}
|
||||
|
||||
// SubResource sets a sub-resource path which can be multiple segments segment after the resource
|
||||
// SubResource sets a sub-resource path which can be multiple segments after the resource
|
||||
// name but before the suffix.
|
||||
func (r *Request) SubResource(subresources ...string) *Request {
|
||||
if r.err != nil {
|
||||
@@ -455,17 +455,9 @@ func (r *Request) URL() *url.URL {
|
||||
|
||||
// finalURLTemplate is similar to URL(), but will make all specific parameter values equal
|
||||
// - instead of name or namespace, "{name}" and "{namespace}" will be used, and all query
|
||||
// parameters will be reset. This creates a copy of the request so as not to change the
|
||||
// underlying object. This means some useful request info (like the types of field
|
||||
// selectors in use) will be lost.
|
||||
// TODO: preserve field selector keys
|
||||
// parameters will be reset. This creates a copy of the url so as not to change the
|
||||
// underlying object.
|
||||
func (r Request) finalURLTemplate() url.URL {
|
||||
if len(r.resourceName) != 0 {
|
||||
r.resourceName = "{name}"
|
||||
}
|
||||
if r.namespaceSet && len(r.namespace) != 0 {
|
||||
r.namespace = "{namespace}"
|
||||
}
|
||||
newParams := url.Values{}
|
||||
v := []string{"{value}"}
|
||||
for k := range r.params {
|
||||
@@ -473,6 +465,59 @@ func (r Request) finalURLTemplate() url.URL {
|
||||
}
|
||||
r.params = newParams
|
||||
url := r.URL()
|
||||
segments := strings.Split(r.URL().Path, "/")
|
||||
groupIndex := 0
|
||||
index := 0
|
||||
if r.URL() != nil && r.baseURL != nil && strings.Contains(r.URL().Path, r.baseURL.Path) {
|
||||
groupIndex += len(strings.Split(r.baseURL.Path, "/"))
|
||||
}
|
||||
if groupIndex >= len(segments) {
|
||||
return *url
|
||||
}
|
||||
|
||||
const CoreGroupPrefix = "api"
|
||||
const NamedGroupPrefix = "apis"
|
||||
isCoreGroup := segments[groupIndex] == CoreGroupPrefix
|
||||
isNamedGroup := segments[groupIndex] == NamedGroupPrefix
|
||||
if isCoreGroup {
|
||||
// checking the case of core group with /api/v1/... format
|
||||
index = groupIndex + 2
|
||||
} else if isNamedGroup {
|
||||
// checking the case of named group with /apis/apps/v1/... format
|
||||
index = groupIndex + 3
|
||||
} else {
|
||||
// this should not happen that the only two possibilities are /api... and /apis..., just want to put an
|
||||
// outlet here in case more API groups are added in future if ever possible:
|
||||
// https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-groups
|
||||
// if a wrong API groups name is encountered, return the {prefix} for url.Path
|
||||
url.Path = "/{prefix}"
|
||||
url.RawQuery = ""
|
||||
return *url
|
||||
}
|
||||
//switch segLength := len(segments) - index; segLength {
|
||||
switch {
|
||||
// case len(segments) - index == 1:
|
||||
// resource (with no name) do nothing
|
||||
case len(segments)-index == 2:
|
||||
// /$RESOURCE/$NAME: replace $NAME with {name}
|
||||
segments[index+1] = "{name}"
|
||||
case len(segments)-index == 3:
|
||||
if segments[index+2] == "finalize" || segments[index+2] == "status" {
|
||||
// /$RESOURCE/$NAME/$SUBRESOURCE: replace $NAME with {name}
|
||||
segments[index+1] = "{name}"
|
||||
} else {
|
||||
// /namespace/$NAMESPACE/$RESOURCE: replace $NAMESPACE with {namespace}
|
||||
segments[index+1] = "{namespace}"
|
||||
}
|
||||
case len(segments)-index >= 4:
|
||||
segments[index+1] = "{namespace}"
|
||||
// /namespace/$NAMESPACE/$RESOURCE/$NAME: replace $NAMESPACE with {namespace}, $NAME with {name}
|
||||
if segments[index+3] != "finalize" && segments[index+3] != "status" {
|
||||
// /$RESOURCE/$NAME/$SUBRESOURCE: replace $NAME with {name}
|
||||
segments[index+3] = "{name}"
|
||||
}
|
||||
}
|
||||
url.Path = path.Join(segments...)
|
||||
return *url
|
||||
}
|
||||
|
||||
|
||||
@@ -340,21 +340,169 @@ func TestResultIntoWithNoBodyReturnsErr(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestURLTemplate(t *testing.T) {
|
||||
uri, _ := url.Parse("http://localhost")
|
||||
r := NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0)
|
||||
r.Prefix("pre1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0")
|
||||
full := r.URL()
|
||||
if full.String() != "http://localhost/pre1/namespaces/ns/r1/nm?p0=v0" {
|
||||
t.Errorf("unexpected initial URL: %s", full)
|
||||
uri, _ := url.Parse("http://localhost/some/base/url/path")
|
||||
testCases := []struct {
|
||||
Request *Request
|
||||
ExpectedFullURL string
|
||||
ExpectedFinalURL string
|
||||
}{
|
||||
{
|
||||
// non dynamic client
|
||||
Request: NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("api", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/nm?p0=v0",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D?p0=%7Bvalue%7D",
|
||||
},
|
||||
{
|
||||
// non dynamic client with wrong api group
|
||||
Request: NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("pre1", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/pre1/v1/namespaces/ns/r1/nm?p0=v0",
|
||||
ExpectedFinalURL: "http://localhost/%7Bprefix%7D",
|
||||
},
|
||||
{
|
||||
// dynamic client with core group + namespace + resourceResource (with name)
|
||||
// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/api/v1/namespaces/ns/r1/name1"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/name1",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + namespace + resourceResource (with name)
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/g1/v1/namespaces/ns/r1/name1"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1/name1",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D",
|
||||
},
|
||||
{
|
||||
// dynamic client with core group + namespace + resourceResource (with NO name)
|
||||
// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/api/v1/namespaces/ns/r1"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + namespace + resourceResource (with NO name)
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/g1/v1/namespaces/ns/r1"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1",
|
||||
},
|
||||
{
|
||||
// dynamic client with core group + resourceResource (with name)
|
||||
// /api/$RESOURCEVERSION/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/api/v1/r1/name1"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/r1/name1",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/r1/%7Bname%7D",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + resourceResource (with name)
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/g1/v1/r1/name1"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/r1/name1",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/r1/%7Bname%7D",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + namespace + resourceResource (with name) + subresource
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D/finalize",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + namespace + resourceResource (with name)
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + namespace + resourceResource (with NO name) + subresource
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/finalize",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + namespace + resourceResource (with NO name) + subresource
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/status",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + namespace + resourceResource (with no name)
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + resourceResource (with name) + subresource
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/finalize"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/finalize",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/finalize",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + resourceResource (with name) + subresource
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/status"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/status",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/status",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + resourceResource (with name)
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces/namespaces"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D",
|
||||
},
|
||||
{
|
||||
// dynamic client with named group + resourceResource (with no name)
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/apis/namespaces/namespaces/namespaces"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces",
|
||||
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces",
|
||||
},
|
||||
{
|
||||
// dynamic client with wrong api group + namespace + resourceResource (with name) + subresource
|
||||
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE
|
||||
Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
|
||||
Prefix("/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
|
||||
ExpectedFullURL: "http://localhost/some/base/url/path/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
|
||||
ExpectedFinalURL: "http://localhost/%7Bprefix%7D",
|
||||
},
|
||||
}
|
||||
actualURL := r.finalURLTemplate()
|
||||
actual := actualURL.String()
|
||||
expected := "http://localhost/pre1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D?p0=%7Bvalue%7D"
|
||||
if actual != expected {
|
||||
t.Errorf("unexpected URL template: %s %s", actual, expected)
|
||||
}
|
||||
if r.URL().String() != full.String() {
|
||||
t.Errorf("creating URL template changed request: %s -> %s", full.String(), r.URL().String())
|
||||
for i, testCase := range testCases {
|
||||
r := testCase.Request
|
||||
full := r.URL()
|
||||
if full.String() != testCase.ExpectedFullURL {
|
||||
t.Errorf("%d: unexpected initial URL: %s %s", i, full, testCase.ExpectedFullURL)
|
||||
}
|
||||
actualURL := r.finalURLTemplate()
|
||||
actual := actualURL.String()
|
||||
if actual != testCase.ExpectedFinalURL {
|
||||
t.Errorf("%d: unexpected URL template: %s %s", i, actual, testCase.ExpectedFinalURL)
|
||||
}
|
||||
if r.URL().String() != full.String() {
|
||||
t.Errorf("%d, creating URL template changed request: %s -> %s", i, full.String(), r.URL().String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,9 +74,10 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
|
||||
KeyFile: c.KeyFile,
|
||||
KeyData: c.KeyData,
|
||||
},
|
||||
Username: c.Username,
|
||||
Password: c.Password,
|
||||
BearerToken: c.BearerToken,
|
||||
Username: c.Username,
|
||||
Password: c.Password,
|
||||
BearerToken: c.BearerToken,
|
||||
BearerTokenFile: c.BearerTokenFile,
|
||||
Impersonate: transport.ImpersonationConfig{
|
||||
UserName: c.Impersonate.UserName,
|
||||
Groups: c.Impersonate.Groups,
|
||||
|
||||
86
tools/cache/listwatch.go
vendored
86
tools/cache/listwatch.go
vendored
@@ -18,17 +18,13 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/pager"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
)
|
||||
|
||||
// ListerWatcher is any object that knows how to perform an initial list and start a watch on a resource.
|
||||
@@ -94,13 +90,6 @@ func NewFilteredListWatchFromClient(c Getter, resource string, namespace string,
|
||||
return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
|
||||
}
|
||||
|
||||
func timeoutFromListOptions(options metav1.ListOptions) time.Duration {
|
||||
if options.TimeoutSeconds != nil {
|
||||
return time.Duration(*options.TimeoutSeconds) * time.Second
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// List a set of apiserver resources
|
||||
func (lw *ListWatch) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
if !lw.DisableChunking {
|
||||
@@ -113,78 +102,3 @@ func (lw *ListWatch) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
func (lw *ListWatch) Watch(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return lw.WatchFunc(options)
|
||||
}
|
||||
|
||||
// ListWatchUntil checks the provided conditions against the items returned by the list watcher, returning wait.ErrWaitTimeout
|
||||
// if timeout is exceeded without all conditions returning true, or an error if an error occurs.
|
||||
// TODO: check for watch expired error and retry watch from latest point? Same issue exists for Until.
|
||||
func ListWatchUntil(timeout time.Duration, lw ListerWatcher, conditions ...watchtools.ConditionFunc) (*watch.Event, error) {
|
||||
if len(conditions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
list, err := lw.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initialItems, err := meta.ExtractList(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use the initial items as simulated "adds"
|
||||
var lastEvent *watch.Event
|
||||
currIndex := 0
|
||||
passedConditions := 0
|
||||
for _, condition := range conditions {
|
||||
// check the next condition against the previous event and short circuit waiting for the next watch
|
||||
if lastEvent != nil {
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
passedConditions = passedConditions + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ConditionSucceeded:
|
||||
for currIndex < len(initialItems) {
|
||||
lastEvent = &watch.Event{Type: watch.Added, Object: initialItems[currIndex]}
|
||||
currIndex++
|
||||
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
passedConditions = passedConditions + 1
|
||||
break ConditionSucceeded
|
||||
}
|
||||
}
|
||||
}
|
||||
if passedConditions == len(conditions) {
|
||||
return lastEvent, nil
|
||||
}
|
||||
remainingConditions := conditions[passedConditions:]
|
||||
|
||||
metaObj, err := meta.ListAccessor(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currResourceVersion := metaObj.GetResourceVersion()
|
||||
|
||||
watchInterface, err := lw.Watch(metav1.ListOptions{ResourceVersion: currResourceVersion})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
evt, err := watchtools.UntilWithoutRetry(ctx, watchInterface, remainingConditions...)
|
||||
if err == watchtools.ErrWatchClosed {
|
||||
// present a consistent error interface to callers
|
||||
err = wait.ErrWaitTimeout
|
||||
}
|
||||
return evt, err
|
||||
}
|
||||
|
||||
27
tools/cache/reflector.go
vendored
27
tools/cache/reflector.go
vendored
@@ -24,10 +24,8 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -95,17 +93,10 @@ func NewReflector(lw ListerWatcher, expectedType interface{}, store Store, resyn
|
||||
return NewNamedReflector(naming.GetNameFromCallsite(internalPackages...), lw, expectedType, store, resyncPeriod)
|
||||
}
|
||||
|
||||
// reflectorDisambiguator is used to disambiguate started reflectors.
|
||||
// initialized to an unstable value to ensure meaning isn't attributed to the suffix.
|
||||
var reflectorDisambiguator = int64(time.Now().UnixNano() % 12345)
|
||||
|
||||
// NewNamedReflector same as NewReflector, but with a specified name for logging
|
||||
func NewNamedReflector(name string, lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector {
|
||||
reflectorSuffix := atomic.AddInt64(&reflectorDisambiguator, 1)
|
||||
r := &Reflector{
|
||||
name: name,
|
||||
// we need this to be unique per process (some names are still the same) but obvious who it belongs to
|
||||
metrics: newReflectorMetrics(makeValidPrometheusMetricLabel(fmt.Sprintf("reflector_"+name+"_%d", reflectorSuffix))),
|
||||
name: name,
|
||||
listerWatcher: lw,
|
||||
store: store,
|
||||
expectedType: reflect.TypeOf(expectedType),
|
||||
@@ -173,13 +164,10 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
// to be served from cache and potentially be delayed relative to
|
||||
// etcd contents. Reflector framework will catch up via Watch() eventually.
|
||||
options := metav1.ListOptions{ResourceVersion: "0"}
|
||||
r.metrics.numberOfLists.Inc()
|
||||
start := r.clock.Now()
|
||||
list, err := r.listerWatcher.List(options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: Failed to list %v: %v", r.name, r.expectedType, err)
|
||||
}
|
||||
r.metrics.listDuration.Observe(time.Since(start).Seconds())
|
||||
listMetaInterface, err := meta.ListAccessor(list)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: Unable to understand list result %#v: %v", r.name, list, err)
|
||||
@@ -189,7 +177,6 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: Unable to understand list result %#v (%v)", r.name, list, err)
|
||||
}
|
||||
r.metrics.numberOfItemsInList.Observe(float64(len(items)))
|
||||
if err := r.syncWith(items, resourceVersion); err != nil {
|
||||
return fmt.Errorf("%s: Unable to sync list result: %v", r.name, err)
|
||||
}
|
||||
@@ -239,7 +226,6 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
TimeoutSeconds: &timeoutSeconds,
|
||||
}
|
||||
|
||||
r.metrics.numberOfWatches.Inc()
|
||||
w, err := r.listerWatcher.Watch(options)
|
||||
if err != nil {
|
||||
switch err {
|
||||
@@ -291,11 +277,6 @@ func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, err
|
||||
// Stopping the watcher should be idempotent and if we return from this function there's no way
|
||||
// we're coming back in with the same watch interface.
|
||||
defer w.Stop()
|
||||
// update metrics
|
||||
defer func() {
|
||||
r.metrics.numberOfItemsInWatch.Observe(float64(eventCount))
|
||||
r.metrics.watchDuration.Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
loop:
|
||||
for {
|
||||
@@ -351,7 +332,6 @@ loop:
|
||||
|
||||
watchDuration := r.clock.Now().Sub(start)
|
||||
if watchDuration < 1*time.Second && eventCount == 0 {
|
||||
r.metrics.numberOfShortWatches.Inc()
|
||||
return fmt.Errorf("very short watch: %s: Unexpected watch close - watch lasted less than a second and no items received", r.name)
|
||||
}
|
||||
glog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedType, eventCount)
|
||||
@@ -370,9 +350,4 @@ func (r *Reflector) setLastSyncResourceVersion(v string) {
|
||||
r.lastSyncResourceVersionMutex.Lock()
|
||||
defer r.lastSyncResourceVersionMutex.Unlock()
|
||||
r.lastSyncResourceVersion = v
|
||||
|
||||
rv, err := strconv.Atoi(v)
|
||||
if err == nil {
|
||||
r.metrics.lastResourceVersion.Set(float64(rv))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
func init() {
|
||||
sDec, _ := base64.StdEncoding.DecodeString("REDACTED+")
|
||||
redactedBytes = []byte(string(sDec))
|
||||
sDec, _ = base64.StdEncoding.DecodeString("DATA+OMITTED")
|
||||
dataOmittedBytes = []byte(string(sDec))
|
||||
}
|
||||
|
||||
// IsConfigEmpty returns true if the config is empty.
|
||||
@@ -79,7 +81,10 @@ func MinifyConfig(config *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var redactedBytes []byte
|
||||
var (
|
||||
redactedBytes []byte
|
||||
dataOmittedBytes []byte
|
||||
)
|
||||
|
||||
// Flatten redacts raw data entries from the config object for a human-readable view.
|
||||
func ShortenConfig(config *Config) {
|
||||
@@ -97,7 +102,7 @@ func ShortenConfig(config *Config) {
|
||||
}
|
||||
for key, cluster := range config.Clusters {
|
||||
if len(cluster.CertificateAuthorityData) > 0 {
|
||||
cluster.CertificateAuthorityData = redactedBytes
|
||||
cluster.CertificateAuthorityData = dataOmittedBytes
|
||||
}
|
||||
config.Clusters[key] = cluster
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ func Example_minifyAndShorten() {
|
||||
// clusters:
|
||||
// cow-cluster:
|
||||
// LocationOfOrigin: ""
|
||||
// certificate-authority-data: REDACTED
|
||||
// certificate-authority-data: DATA+OMITTED
|
||||
// server: http://cow.org:8080
|
||||
// contexts:
|
||||
// federal-context:
|
||||
@@ -276,14 +276,15 @@ func TestShortenSuccess(t *testing.T) {
|
||||
}
|
||||
|
||||
redacted := string(redactedBytes)
|
||||
dataOmitted := string(dataOmittedBytes)
|
||||
if len(mutatingConfig.Clusters) != 2 {
|
||||
t.Errorf("unexpected clusters: %v", mutatingConfig.Clusters)
|
||||
}
|
||||
if !reflect.DeepEqual(startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster]) {
|
||||
t.Errorf("expected %v, got %v", startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster])
|
||||
}
|
||||
if string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData) != redacted {
|
||||
t.Errorf("expected %v, got %v", redacted, string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData))
|
||||
if string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData) != dataOmitted {
|
||||
t.Errorf("expected %v, got %v", dataOmitted, string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData))
|
||||
}
|
||||
|
||||
if len(mutatingConfig.AuthInfos) != 2 {
|
||||
|
||||
@@ -175,10 +175,6 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
// only try to read the auth information if we are secure
|
||||
if restclient.IsConfigTransportTLS(*clientConfig) {
|
||||
var err error
|
||||
|
||||
// mergo is a first write wins for map value and a last writing wins for interface values
|
||||
// NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
|
||||
// Our mergo.Merge version is older than this change.
|
||||
var persister restclient.AuthProviderConfigPersister
|
||||
if config.configAccess != nil {
|
||||
authInfoName, _ := config.getAuthInfoName()
|
||||
@@ -188,13 +184,13 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergo.Merge(clientConfig, userAuthPartialConfig)
|
||||
mergo.MergeWithOverwrite(clientConfig, userAuthPartialConfig)
|
||||
|
||||
serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergo.Merge(clientConfig, serverAuthPartialConfig)
|
||||
mergo.MergeWithOverwrite(clientConfig, serverAuthPartialConfig)
|
||||
}
|
||||
|
||||
return clientConfig, nil
|
||||
@@ -214,7 +210,7 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo,
|
||||
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
|
||||
configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
|
||||
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
|
||||
mergo.Merge(mergedConfig, configClientConfig)
|
||||
mergo.MergeWithOverwrite(mergedConfig, configClientConfig)
|
||||
|
||||
return mergedConfig, nil
|
||||
}
|
||||
@@ -232,12 +228,14 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
||||
// blindly overwrite existing values based on precedence
|
||||
if len(configAuthInfo.Token) > 0 {
|
||||
mergedConfig.BearerToken = configAuthInfo.Token
|
||||
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
||||
} else if len(configAuthInfo.TokenFile) > 0 {
|
||||
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergedConfig.BearerToken = string(tokenBytes)
|
||||
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
||||
}
|
||||
if len(configAuthInfo.Impersonate) > 0 {
|
||||
mergedConfig.Impersonate = restclient.ImpersonationConfig{
|
||||
@@ -279,8 +277,8 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
||||
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
|
||||
previouslyMergedConfig := mergedConfig
|
||||
mergedConfig = &restclient.Config{}
|
||||
mergo.Merge(mergedConfig, promptedConfig)
|
||||
mergo.Merge(mergedConfig, previouslyMergedConfig)
|
||||
mergo.MergeWithOverwrite(mergedConfig, promptedConfig)
|
||||
mergo.MergeWithOverwrite(mergedConfig, previouslyMergedConfig)
|
||||
config.promptedCredentials.username = mergedConfig.Username
|
||||
config.promptedCredentials.password = mergedConfig.Password
|
||||
}
|
||||
@@ -423,11 +421,11 @@ func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
|
||||
|
||||
mergedContext := clientcmdapi.NewContext()
|
||||
if configContext, exists := contexts[contextName]; exists {
|
||||
mergo.Merge(mergedContext, configContext)
|
||||
mergo.MergeWithOverwrite(mergedContext, configContext)
|
||||
} else if required {
|
||||
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
|
||||
}
|
||||
mergo.Merge(mergedContext, config.overrides.Context)
|
||||
mergo.MergeWithOverwrite(mergedContext, config.overrides.Context)
|
||||
|
||||
return *mergedContext, nil
|
||||
}
|
||||
@@ -439,11 +437,11 @@ func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
|
||||
|
||||
mergedAuthInfo := clientcmdapi.NewAuthInfo()
|
||||
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
||||
mergo.Merge(mergedAuthInfo, configAuthInfo)
|
||||
mergo.MergeWithOverwrite(mergedAuthInfo, configAuthInfo)
|
||||
} else if required {
|
||||
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
|
||||
}
|
||||
mergo.Merge(mergedAuthInfo, config.overrides.AuthInfo)
|
||||
mergo.MergeWithOverwrite(mergedAuthInfo, config.overrides.AuthInfo)
|
||||
|
||||
return *mergedAuthInfo, nil
|
||||
}
|
||||
@@ -454,13 +452,13 @@ func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
|
||||
clusterInfoName, required := config.getClusterName()
|
||||
|
||||
mergedClusterInfo := clientcmdapi.NewCluster()
|
||||
mergo.Merge(mergedClusterInfo, config.overrides.ClusterDefaults)
|
||||
mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterDefaults)
|
||||
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
||||
mergo.Merge(mergedClusterInfo, configClusterInfo)
|
||||
mergo.MergeWithOverwrite(mergedClusterInfo, configClusterInfo)
|
||||
} else if required {
|
||||
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
|
||||
}
|
||||
mergo.Merge(mergedClusterInfo, config.overrides.ClusterInfo)
|
||||
mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterInfo)
|
||||
// An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
|
||||
// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
|
||||
caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
|
||||
@@ -502,8 +500,9 @@ func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error)
|
||||
if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
|
||||
icc.Host = server
|
||||
}
|
||||
if token := config.overrides.AuthInfo.Token; len(token) > 0 {
|
||||
icc.BearerToken = token
|
||||
if len(config.overrides.AuthInfo.Token) > 0 || len(config.overrides.AuthInfo.TokenFile) > 0 {
|
||||
icc.BearerToken = config.overrides.AuthInfo.Token
|
||||
icc.BearerTokenFile = config.overrides.AuthInfo.TokenFile
|
||||
}
|
||||
if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
|
||||
icc.TLSClientConfig.CAFile = certificateAuthorityFile
|
||||
|
||||
@@ -28,21 +28,83 @@ import (
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func TestOldMergoLib(t *testing.T) {
|
||||
type T struct {
|
||||
X string
|
||||
func TestMergoSemantics(t *testing.T) {
|
||||
type U struct {
|
||||
A string
|
||||
B int64
|
||||
}
|
||||
dst := T{X: "one"}
|
||||
src := T{X: "two"}
|
||||
mergo.Merge(&dst, &src)
|
||||
if dst.X != "two" {
|
||||
// mergo.Merge changed in an incompatible way with
|
||||
//
|
||||
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
|
||||
//
|
||||
// We have to stay with the old version which still does eager
|
||||
// copying from src to dst in structs.
|
||||
t.Errorf("mergo.Merge library found with incompatible, new behavior")
|
||||
type T struct {
|
||||
S []string
|
||||
X string
|
||||
Y int64
|
||||
U U
|
||||
}
|
||||
var testDataStruct = []struct {
|
||||
dst T
|
||||
src T
|
||||
expected T
|
||||
}{
|
||||
{
|
||||
dst: T{X: "one"},
|
||||
src: T{X: "two"},
|
||||
expected: T{X: "two"},
|
||||
},
|
||||
{
|
||||
dst: T{X: "one", Y: 5, U: U{A: "four", B: 6}},
|
||||
src: T{X: "two", U: U{A: "three", B: 4}},
|
||||
expected: T{X: "two", Y: 5, U: U{A: "three", B: 4}},
|
||||
},
|
||||
{
|
||||
dst: T{S: []string{"test3", "test4", "test5"}},
|
||||
src: T{S: []string{"test1", "test2", "test3"}},
|
||||
expected: T{S: []string{"test1", "test2", "test3"}},
|
||||
},
|
||||
}
|
||||
for _, data := range testDataStruct {
|
||||
err := mergo.MergeWithOverwrite(&data.dst, &data.src)
|
||||
if err != nil {
|
||||
t.Errorf("error while merging: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(data.dst, data.expected) {
|
||||
// The mergo library has previously changed in a an incompatible way.
|
||||
// example:
|
||||
//
|
||||
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
|
||||
//
|
||||
// This test verifies that the semantics of the merge are what we expect.
|
||||
// If they are not, the mergo library may have been updated and broken
|
||||
// unexpectedly.
|
||||
t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
|
||||
}
|
||||
}
|
||||
|
||||
var testDataMap = []struct {
|
||||
dst map[string]int
|
||||
src map[string]int
|
||||
expected map[string]int
|
||||
}{
|
||||
{
|
||||
dst: map[string]int{"rsc": 6543, "r": 2138, "gri": 1908, "adg": 912, "prt": 22},
|
||||
src: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912},
|
||||
expected: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912, "prt": 22},
|
||||
},
|
||||
}
|
||||
for _, data := range testDataMap {
|
||||
err := mergo.MergeWithOverwrite(&data.dst, &data.src)
|
||||
if err != nil {
|
||||
t.Errorf("error while merging: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(data.dst, data.expected) {
|
||||
// The mergo library has previously changed in a an incompatible way.
|
||||
// example:
|
||||
//
|
||||
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
|
||||
//
|
||||
// This test verifies that the semantics of the merge are what we expect.
|
||||
// If they are not, the mergo library may have been updated and broken
|
||||
// unexpectedly.
|
||||
t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,9 +194,17 @@ func TestModifyContext(t *testing.T) {
|
||||
"clean": true,
|
||||
}
|
||||
|
||||
tempPath, err := ioutil.TempFile("", "testclientcmd-")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.Remove(tempPath.Name())
|
||||
|
||||
pathOptions := NewDefaultPathOptions()
|
||||
config := createValidTestConfig()
|
||||
|
||||
pathOptions.GlobalFile = tempPath.Name()
|
||||
|
||||
// define new context and assign it - our path options config
|
||||
config.Contexts["updated"] = &clientcmdapi.Context{
|
||||
Cluster: "updated",
|
||||
@@ -477,6 +547,30 @@ func TestInClusterClientConfigPrecedence(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
Server: "https://host-from-overrides.com",
|
||||
CertificateAuthority: "/path/to/ca-from-overrides.crt",
|
||||
},
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
Token: "token-from-override",
|
||||
TokenFile: "tokenfile-from-override",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
Server: "https://host-from-overrides.com",
|
||||
CertificateAuthority: "/path/to/ca-from-overrides.crt",
|
||||
},
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
Token: "",
|
||||
TokenFile: "tokenfile-from-override",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{},
|
||||
},
|
||||
@@ -485,13 +579,15 @@ func TestInClusterClientConfigPrecedence(t *testing.T) {
|
||||
for _, tc := range tt {
|
||||
expectedServer := "https://host-from-cluster.com"
|
||||
expectedToken := "token-from-cluster"
|
||||
expectedTokenFile := "tokenfile-from-cluster"
|
||||
expectedCAFile := "/path/to/ca-from-cluster.crt"
|
||||
|
||||
icc := &inClusterClientConfig{
|
||||
inClusterConfigProvider: func() (*restclient.Config, error) {
|
||||
return &restclient.Config{
|
||||
Host: expectedServer,
|
||||
BearerToken: expectedToken,
|
||||
Host: expectedServer,
|
||||
BearerToken: expectedToken,
|
||||
BearerTokenFile: expectedTokenFile,
|
||||
TLSClientConfig: restclient.TLSClientConfig{
|
||||
CAFile: expectedCAFile,
|
||||
},
|
||||
@@ -508,8 +604,9 @@ func TestInClusterClientConfigPrecedence(t *testing.T) {
|
||||
if overridenServer := tc.overrides.ClusterInfo.Server; len(overridenServer) > 0 {
|
||||
expectedServer = overridenServer
|
||||
}
|
||||
if overridenToken := tc.overrides.AuthInfo.Token; len(overridenToken) > 0 {
|
||||
expectedToken = overridenToken
|
||||
if len(tc.overrides.AuthInfo.Token) > 0 || len(tc.overrides.AuthInfo.TokenFile) > 0 {
|
||||
expectedToken = tc.overrides.AuthInfo.Token
|
||||
expectedTokenFile = tc.overrides.AuthInfo.TokenFile
|
||||
}
|
||||
if overridenCAFile := tc.overrides.ClusterInfo.CertificateAuthority; len(overridenCAFile) > 0 {
|
||||
expectedCAFile = overridenCAFile
|
||||
@@ -521,6 +618,9 @@ func TestInClusterClientConfigPrecedence(t *testing.T) {
|
||||
if clientConfig.BearerToken != expectedToken {
|
||||
t.Errorf("Expected token %v, got %v", expectedToken, clientConfig.BearerToken)
|
||||
}
|
||||
if clientConfig.BearerTokenFile != expectedTokenFile {
|
||||
t.Errorf("Expected tokenfile %v, got %v", expectedTokenFile, clientConfig.BearerTokenFile)
|
||||
}
|
||||
if clientConfig.TLSClientConfig.CAFile != expectedCAFile {
|
||||
t.Errorf("Expected Certificate Authority %v, got %v", expectedCAFile, clientConfig.TLSClientConfig.CAFile)
|
||||
}
|
||||
@@ -566,3 +666,46 @@ func TestNamespaceOverride(t *testing.T) {
|
||||
|
||||
matchStringArg("foo", ns, t)
|
||||
}
|
||||
|
||||
func TestAuthConfigMerge(t *testing.T) {
|
||||
content := `
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://localhost:8080
|
||||
name: foo-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: foo-cluster
|
||||
user: foo-user
|
||||
namespace: bar
|
||||
name: foo-context
|
||||
current-context: foo-context
|
||||
kind: Config
|
||||
users:
|
||||
- name: foo-user
|
||||
user:
|
||||
exec:
|
||||
apiVersion: client.authentication.k8s.io/v1alpha1
|
||||
args:
|
||||
- arg-1
|
||||
- arg-2
|
||||
command: foo-command
|
||||
`
|
||||
tmpfile, err := ioutil.TempFile("", "kubeconfig")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
if err := ioutil.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
config, err := BuildConfigFromFlags("", tmpfile.Name())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(config.ExecProvider.Args, []string{"arg-1", "arg-2"}) {
|
||||
t.Errorf("Got args %v when they should be %v\n", config.ExecProvider.Args, []string{"arg-1", "arg-2"})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
||||
mapConfig := clientcmdapi.NewConfig()
|
||||
|
||||
for _, kubeconfig := range kubeconfigs {
|
||||
mergo.Merge(mapConfig, kubeconfig)
|
||||
mergo.MergeWithOverwrite(mapConfig, kubeconfig)
|
||||
}
|
||||
|
||||
// merge all of the struct values in the reverse order so that priority is given correctly
|
||||
@@ -219,14 +219,14 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
||||
nonMapConfig := clientcmdapi.NewConfig()
|
||||
for i := len(kubeconfigs) - 1; i >= 0; i-- {
|
||||
kubeconfig := kubeconfigs[i]
|
||||
mergo.Merge(nonMapConfig, kubeconfig)
|
||||
mergo.MergeWithOverwrite(nonMapConfig, kubeconfig)
|
||||
}
|
||||
|
||||
// since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and
|
||||
// get the values we expect.
|
||||
config := clientcmdapi.NewConfig()
|
||||
mergo.Merge(config, mapConfig)
|
||||
mergo.Merge(config, nonMapConfig)
|
||||
mergo.MergeWithOverwrite(config, mapConfig)
|
||||
mergo.MergeWithOverwrite(config, nonMapConfig)
|
||||
|
||||
if rules.ResolvePaths() {
|
||||
if err := ResolveLocalPaths(config); err != nil {
|
||||
|
||||
69
tools/leaderelection/healthzadaptor.go
Normal file
69
tools/leaderelection/healthzadaptor.go
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes 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 leaderelection
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HealthzAdaptor associates the /healthz endpoint with the LeaderElection object.
|
||||
// It helps deal with the /healthz endpoint being set up prior to the LeaderElection.
|
||||
// This contains the code needed to act as an adaptor between the leader
|
||||
// election code the health check code. It allows us to provide health
|
||||
// status about the leader election. Most specifically about if the leader
|
||||
// has failed to renew without exiting the process. In that case we should
|
||||
// report not healthy and rely on the kubelet to take down the process.
|
||||
type HealthzAdaptor struct {
|
||||
pointerLock sync.Mutex
|
||||
le *LeaderElector
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// Name returns the name of the health check we are implementing.
|
||||
func (l *HealthzAdaptor) Name() string {
|
||||
return "leaderElection"
|
||||
}
|
||||
|
||||
// Check is called by the healthz endpoint handler.
|
||||
// It fails (returns an error) if we own the lease but had not been able to renew it.
|
||||
func (l *HealthzAdaptor) Check(req *http.Request) error {
|
||||
l.pointerLock.Lock()
|
||||
defer l.pointerLock.Unlock()
|
||||
if l.le == nil {
|
||||
return nil
|
||||
}
|
||||
return l.le.Check(l.timeout)
|
||||
}
|
||||
|
||||
// SetLeaderElection ties a leader election object to a HealthzAdaptor
|
||||
func (l *HealthzAdaptor) SetLeaderElection(le *LeaderElector) {
|
||||
l.pointerLock.Lock()
|
||||
defer l.pointerLock.Unlock()
|
||||
l.le = le
|
||||
}
|
||||
|
||||
// NewLeaderHealthzAdaptor creates a basic healthz adaptor to monitor a leader election.
|
||||
// timeout determines the time beyond the lease expiry to be allowed for timeout.
|
||||
// checks within the timeout period after the lease expires will still return healthy.
|
||||
func NewLeaderHealthzAdaptor(timeout time.Duration) *HealthzAdaptor {
|
||||
result := &HealthzAdaptor{
|
||||
timeout: timeout,
|
||||
}
|
||||
return result
|
||||
}
|
||||
175
tools/leaderelection/healthzadaptor_test.go
Normal file
175
tools/leaderelection/healthzadaptor_test.go
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes 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 leaderelection
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
rl "k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type fakeLock struct {
|
||||
identity string
|
||||
}
|
||||
|
||||
// Get is a dummy to allow us to have a fakeLock for testing.
|
||||
func (fl *fakeLock) Get() (ler *rl.LeaderElectionRecord, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create is a dummy to allow us to have a fakeLock for testing.
|
||||
func (fl *fakeLock) Create(ler rl.LeaderElectionRecord) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update is a dummy to allow us to have a fakeLock for testing.
|
||||
func (fl *fakeLock) Update(ler rl.LeaderElectionRecord) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordEvent is a dummy to allow us to have a fakeLock for testing.
|
||||
func (fl *fakeLock) RecordEvent(string) {}
|
||||
|
||||
// Identity is a dummy to allow us to have a fakeLock for testing.
|
||||
func (fl *fakeLock) Identity() string {
|
||||
return fl.identity
|
||||
}
|
||||
|
||||
// Describe is a dummy to allow us to have a fakeLock for testing.
|
||||
func (fl *fakeLock) Describe() string {
|
||||
return "Dummy implementation of lock for testing"
|
||||
}
|
||||
|
||||
// TestLeaderElectionHealthChecker tests that the healthcheck for leader election handles its edge cases.
|
||||
func TestLeaderElectionHealthChecker(t *testing.T) {
|
||||
current := time.Now()
|
||||
req := &http.Request{}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
expected error
|
||||
adaptorTimeout time.Duration
|
||||
elector *LeaderElector
|
||||
}{
|
||||
{
|
||||
description: "call check before leader elector initialized",
|
||||
expected: nil,
|
||||
adaptorTimeout: time.Second * 20,
|
||||
elector: nil,
|
||||
},
|
||||
{
|
||||
description: "call check when the the lease is far expired",
|
||||
expected: fmt.Errorf("failed election to renew leadership on lease %s", "foo"),
|
||||
adaptorTimeout: time.Second * 20,
|
||||
elector: &LeaderElector{
|
||||
config: LeaderElectionConfig{
|
||||
Lock: &fakeLock{identity: "healthTest"},
|
||||
LeaseDuration: time.Minute,
|
||||
Name: "foo",
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{
|
||||
HolderIdentity: "healthTest",
|
||||
},
|
||||
observedTime: current,
|
||||
clock: clock.NewFakeClock(current.Add(time.Hour)),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "call check when the the lease is far expired but held by another server",
|
||||
expected: nil,
|
||||
adaptorTimeout: time.Second * 20,
|
||||
elector: &LeaderElector{
|
||||
config: LeaderElectionConfig{
|
||||
Lock: &fakeLock{identity: "healthTest"},
|
||||
LeaseDuration: time.Minute,
|
||||
Name: "foo",
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{
|
||||
HolderIdentity: "otherServer",
|
||||
},
|
||||
observedTime: current,
|
||||
clock: clock.NewFakeClock(current.Add(time.Hour)),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "call check when the the lease is not expired",
|
||||
expected: nil,
|
||||
adaptorTimeout: time.Second * 20,
|
||||
elector: &LeaderElector{
|
||||
config: LeaderElectionConfig{
|
||||
Lock: &fakeLock{identity: "healthTest"},
|
||||
LeaseDuration: time.Minute,
|
||||
Name: "foo",
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{
|
||||
HolderIdentity: "healthTest",
|
||||
},
|
||||
observedTime: current,
|
||||
clock: clock.NewFakeClock(current),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "call check when the the lease is expired but inside the timeout",
|
||||
expected: nil,
|
||||
adaptorTimeout: time.Second * 20,
|
||||
elector: &LeaderElector{
|
||||
config: LeaderElectionConfig{
|
||||
Lock: &fakeLock{identity: "healthTest"},
|
||||
LeaseDuration: time.Minute,
|
||||
Name: "foo",
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{
|
||||
HolderIdentity: "healthTest",
|
||||
},
|
||||
observedTime: current,
|
||||
clock: clock.NewFakeClock(current.Add(time.Minute).Add(time.Second)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
adaptor := NewLeaderHealthzAdaptor(test.adaptorTimeout)
|
||||
if adaptor.le != nil {
|
||||
t.Errorf("[%s] leaderChecker started with a LeaderElector %v", test.description, adaptor.le)
|
||||
}
|
||||
if test.elector != nil {
|
||||
test.elector.config.WatchDog = adaptor
|
||||
adaptor.SetLeaderElection(test.elector)
|
||||
if adaptor.le == nil {
|
||||
t.Errorf("[%s] adaptor failed to set the LeaderElector", test.description)
|
||||
}
|
||||
}
|
||||
err := adaptor.Check(req)
|
||||
if test.expected == nil {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
t.Errorf("[%s] called check, expected no error but received \"%v\"", test.description, err)
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("[%s] called check and failed to received the expected error \"%v\"", test.description, test.expected)
|
||||
}
|
||||
if err.Error() != test.expected.Error() {
|
||||
t.Errorf("[%s] called check, expected %v, received %v", test.description, test.expected, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
rl "k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||
@@ -90,6 +91,7 @@ func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) {
|
||||
}
|
||||
return &LeaderElector{
|
||||
config: lec,
|
||||
clock: clock.RealClock{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -111,6 +113,13 @@ type LeaderElectionConfig struct {
|
||||
// Callbacks are callbacks that are triggered during certain lifecycle
|
||||
// events of the LeaderElector
|
||||
Callbacks LeaderCallbacks
|
||||
|
||||
// WatchDog is the associated health checker
|
||||
// WatchDog may be null if its not needed/configured.
|
||||
WatchDog *HealthzAdaptor
|
||||
|
||||
// Name is the name of the resource lock for debugging
|
||||
Name string
|
||||
}
|
||||
|
||||
// LeaderCallbacks are callbacks that are triggered during certain
|
||||
@@ -139,6 +148,12 @@ type LeaderElector struct {
|
||||
// value observedRecord.HolderIdentity if the transition has
|
||||
// not yet been reported.
|
||||
reportedLeader string
|
||||
|
||||
// clock is wrapper around time to allow for less flaky testing
|
||||
clock clock.Clock
|
||||
|
||||
// name is the name of the resource lock for debugging
|
||||
name string
|
||||
}
|
||||
|
||||
// Run starts the leader election loop
|
||||
@@ -163,6 +178,9 @@ func RunOrDie(ctx context.Context, lec LeaderElectionConfig) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if lec.WatchDog != nil {
|
||||
lec.WatchDog.SetLeaderElection(le)
|
||||
}
|
||||
le.Run(ctx)
|
||||
}
|
||||
|
||||
@@ -257,14 +275,14 @@ func (le *LeaderElector) tryAcquireOrRenew() bool {
|
||||
return false
|
||||
}
|
||||
le.observedRecord = leaderElectionRecord
|
||||
le.observedTime = time.Now()
|
||||
le.observedTime = le.clock.Now()
|
||||
return true
|
||||
}
|
||||
|
||||
// 2. Record obtained, check the Identity & Time
|
||||
if !reflect.DeepEqual(le.observedRecord, *oldLeaderElectionRecord) {
|
||||
le.observedRecord = *oldLeaderElectionRecord
|
||||
le.observedTime = time.Now()
|
||||
le.observedTime = le.clock.Now()
|
||||
}
|
||||
if le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&
|
||||
!le.IsLeader() {
|
||||
@@ -287,7 +305,7 @@ func (le *LeaderElector) tryAcquireOrRenew() bool {
|
||||
return false
|
||||
}
|
||||
le.observedRecord = leaderElectionRecord
|
||||
le.observedTime = time.Now()
|
||||
le.observedTime = le.clock.Now()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -300,3 +318,19 @@ func (le *LeaderElector) maybeReportTransition() {
|
||||
go le.config.Callbacks.OnNewLeader(le.reportedLeader)
|
||||
}
|
||||
}
|
||||
|
||||
// Check will determine if the current lease is expired by more than timeout.
|
||||
func (le *LeaderElector) Check(maxTolerableExpiredLease time.Duration) error {
|
||||
if !le.IsLeader() {
|
||||
// Currently not concerned with the case that we are hot standby
|
||||
return nil
|
||||
}
|
||||
// If we are more than timeout seconds after the lease duration that is past the timeout
|
||||
// on the lease renew. Time to start reporting ourselves as unhealthy. We should have
|
||||
// died but conditions like deadlock can prevent this. (See #70819)
|
||||
if le.clock.Since(le.observedTime) > le.config.LeaseDuration+maxTolerableExpiredLease {
|
||||
return fmt.Errorf("failed election to renew leadership on lease %s", le.config.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
fakecorev1 "k8s.io/client-go/kubernetes/typed/core/v1/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
rl "k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||
@@ -257,6 +258,7 @@ func testTryAcquireOrRenew(t *testing.T, objectType string) {
|
||||
config: lec,
|
||||
observedRecord: test.observedRecord,
|
||||
observedTime: test.observedTime,
|
||||
clock: clock.RealClock{},
|
||||
}
|
||||
|
||||
if test.expectSuccess != le.tryAcquireOrRenew() {
|
||||
|
||||
@@ -84,11 +84,6 @@ func getSpamKey(event *v1.Event) string {
|
||||
// EventFilterFunc is a function that returns true if the event should be skipped
|
||||
type EventFilterFunc func(event *v1.Event) bool
|
||||
|
||||
// DefaultEventFilterFunc returns false for all incoming events
|
||||
func DefaultEventFilterFunc(event *v1.Event) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// EventSourceObjectSpamFilter is responsible for throttling
|
||||
// the amount of events a source and object can produce.
|
||||
type EventSourceObjectSpamFilter struct {
|
||||
|
||||
@@ -127,14 +127,6 @@ func validateEvent(messagePrefix string, actualEvent *v1.Event, expectedEvent *v
|
||||
return actualEvent, nil
|
||||
}
|
||||
|
||||
// TestDefaultEventFilterFunc ensures that no events are filtered
|
||||
func TestDefaultEventFilterFunc(t *testing.T) {
|
||||
event := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other"))
|
||||
if DefaultEventFilterFunc(&event) {
|
||||
t.Fatalf("DefaultEventFilterFunc should always return false")
|
||||
}
|
||||
}
|
||||
|
||||
// TestEventAggregatorByReasonFunc ensures that two events are aggregated if they vary only by event.message
|
||||
func TestEventAggregatorByReasonFunc(t *testing.T) {
|
||||
event1 := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other"))
|
||||
|
||||
114
tools/watch/informerwatcher.go
Normal file
114
tools/watch/informerwatcher.go
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes 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 watch
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
func newTicketer() *ticketer {
|
||||
return &ticketer{
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
}
|
||||
}
|
||||
|
||||
type ticketer struct {
|
||||
counter uint64
|
||||
|
||||
cond *sync.Cond
|
||||
current uint64
|
||||
}
|
||||
|
||||
func (t *ticketer) GetTicket() uint64 {
|
||||
// -1 to start from 0
|
||||
return atomic.AddUint64(&t.counter, 1) - 1
|
||||
}
|
||||
|
||||
func (t *ticketer) WaitForTicket(ticket uint64, f func()) {
|
||||
t.cond.L.Lock()
|
||||
defer t.cond.L.Unlock()
|
||||
for ticket != t.current {
|
||||
t.cond.Wait()
|
||||
}
|
||||
|
||||
f()
|
||||
|
||||
t.current++
|
||||
t.cond.Broadcast()
|
||||
}
|
||||
|
||||
// NewIndexerInformerWatcher will create an IndexerInformer and wrap it into watch.Interface
|
||||
// so you can use it anywhere where you'd have used a regular Watcher returned from Watch method.
|
||||
func NewIndexerInformerWatcher(lw cache.ListerWatcher, objType runtime.Object) (cache.Indexer, cache.Controller, watch.Interface) {
|
||||
ch := make(chan watch.Event)
|
||||
w := watch.NewProxyWatcher(ch)
|
||||
t := newTicketer()
|
||||
|
||||
indexer, informer := cache.NewIndexerInformer(lw, objType, 0, cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
go t.WaitForTicket(t.GetTicket(), func() {
|
||||
select {
|
||||
case ch <- watch.Event{
|
||||
Type: watch.Added,
|
||||
Object: obj.(runtime.Object),
|
||||
}:
|
||||
case <-w.StopChan():
|
||||
}
|
||||
})
|
||||
},
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
go t.WaitForTicket(t.GetTicket(), func() {
|
||||
select {
|
||||
case ch <- watch.Event{
|
||||
Type: watch.Modified,
|
||||
Object: new.(runtime.Object),
|
||||
}:
|
||||
case <-w.StopChan():
|
||||
}
|
||||
})
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
go t.WaitForTicket(t.GetTicket(), func() {
|
||||
staleObj, stale := obj.(cache.DeletedFinalStateUnknown)
|
||||
if stale {
|
||||
// We have no means of passing the additional information down using watch API based on watch.Event
|
||||
// but the caller can filter such objects by checking if metadata.deletionTimestamp is set
|
||||
obj = staleObj
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- watch.Event{
|
||||
Type: watch.Deleted,
|
||||
Object: obj.(runtime.Object),
|
||||
}:
|
||||
case <-w.StopChan():
|
||||
}
|
||||
})
|
||||
},
|
||||
}, cache.Indexers{})
|
||||
|
||||
go func() {
|
||||
informer.Run(w.StopChan())
|
||||
}()
|
||||
|
||||
return indexer, informer, w
|
||||
}
|
||||
236
tools/watch/informerwatcher_test.go
Normal file
236
tools/watch/informerwatcher_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes 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 watch
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||
testcore "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type byEventTypeAndName []watch.Event
|
||||
|
||||
func (a byEventTypeAndName) Len() int { return len(a) }
|
||||
func (a byEventTypeAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byEventTypeAndName) Less(i, j int) bool {
|
||||
if a[i].Type < a[j].Type {
|
||||
return true
|
||||
}
|
||||
|
||||
if a[i].Type > a[j].Type {
|
||||
return false
|
||||
}
|
||||
|
||||
return a[i].Object.(*corev1.Secret).Name < a[j].Object.(*corev1.Secret).Name
|
||||
}
|
||||
|
||||
func TestTicketer(t *testing.T) {
|
||||
tg := newTicketer()
|
||||
|
||||
const numTickets = 100 // current golang limit for race detector is 8192 simultaneously alive goroutines
|
||||
var tickets []uint64
|
||||
for i := 0; i < numTickets; i++ {
|
||||
ticket := tg.GetTicket()
|
||||
tickets = append(tickets, ticket)
|
||||
|
||||
exp, got := uint64(i), ticket
|
||||
if got != exp {
|
||||
t.Fatalf("expected ticket %d, got %d", exp, got)
|
||||
}
|
||||
}
|
||||
|
||||
// shuffle tickets
|
||||
rand.Shuffle(len(tickets), func(i, j int) {
|
||||
tickets[i], tickets[j] = tickets[j], tickets[i]
|
||||
})
|
||||
|
||||
res := make(chan uint64, len(tickets))
|
||||
for _, ticket := range tickets {
|
||||
go func(ticket uint64) {
|
||||
time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
|
||||
tg.WaitForTicket(ticket, func() {
|
||||
res <- ticket
|
||||
})
|
||||
}(ticket)
|
||||
}
|
||||
|
||||
for i := 0; i < numTickets; i++ {
|
||||
exp, got := uint64(i), <-res
|
||||
if got != exp {
|
||||
t.Fatalf("expected ticket %d, got %d", exp, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInformerWatcher(t *testing.T) {
|
||||
// Make sure there are no 2 same types of events on a secret with the same name or that might be flaky.
|
||||
tt := []struct {
|
||||
name string
|
||||
objects []runtime.Object
|
||||
events []watch.Event
|
||||
}{
|
||||
{
|
||||
name: "basic test",
|
||||
objects: []runtime.Object{
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-1",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-1": "initial",
|
||||
},
|
||||
},
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-2",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-2": "initial",
|
||||
},
|
||||
},
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-3",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-3": "initial",
|
||||
},
|
||||
},
|
||||
},
|
||||
events: []watch.Event{
|
||||
{
|
||||
Type: watch.Added,
|
||||
Object: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-4",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-4": "initial",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: watch.Modified,
|
||||
Object: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-2",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-2": "new",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: watch.Deleted,
|
||||
Object: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var expected []watch.Event
|
||||
for _, o := range tc.objects {
|
||||
expected = append(expected, watch.Event{
|
||||
Type: watch.Added,
|
||||
Object: o.DeepCopyObject(),
|
||||
})
|
||||
}
|
||||
for _, e := range tc.events {
|
||||
expected = append(expected, *e.DeepCopy())
|
||||
}
|
||||
|
||||
fake := fakeclientset.NewSimpleClientset(tc.objects...)
|
||||
fakeWatch := watch.NewFakeWithChanSize(len(tc.events), false)
|
||||
fake.PrependWatchReactor("secrets", testcore.DefaultWatchReactor(fakeWatch, nil))
|
||||
|
||||
for _, e := range tc.events {
|
||||
fakeWatch.Action(e.Type, e.Object)
|
||||
}
|
||||
|
||||
lw := &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return fake.Core().Secrets("").List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return fake.Core().Secrets("").Watch(options)
|
||||
},
|
||||
}
|
||||
_, _, w := NewIndexerInformerWatcher(lw, &corev1.Secret{})
|
||||
|
||||
var result []watch.Event
|
||||
loop:
|
||||
for {
|
||||
var event watch.Event
|
||||
var ok bool
|
||||
select {
|
||||
case event, ok = <-w.ResultChan():
|
||||
if !ok {
|
||||
t.Errorf("Failed to read event: channel is already closed!")
|
||||
return
|
||||
}
|
||||
|
||||
result = append(result, *event.DeepCopy())
|
||||
case <-time.After(time.Second * 1):
|
||||
// All the events are buffered -> this means we are done
|
||||
// Also the one sec will make sure that we would detect RetryWatcher's incorrect behaviour after last event
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// Informers don't guarantee event order so we need to sort these arrays to compare them
|
||||
sort.Sort(byEventTypeAndName(expected))
|
||||
sort.Sort(byEventTypeAndName(result))
|
||||
|
||||
if !reflect.DeepEqual(expected, result) {
|
||||
t.Error(spew.Errorf("\nexpected: %#v,\ngot: %#v,\ndiff: %s", expected, result, diff.ObjectReflectDiff(expected, result)))
|
||||
return
|
||||
}
|
||||
|
||||
// Fill in some data to test watch closing while there are some events to be read
|
||||
for _, e := range tc.events {
|
||||
fakeWatch.Action(e.Type, e.Object)
|
||||
}
|
||||
|
||||
// Stop before reading all the data to make sure the informer can deal with closed channel
|
||||
w.Stop()
|
||||
|
||||
// Wait a bit to see if the informer won't panic
|
||||
// TODO: Try to figure out a more reliable mechanism than time.Sleep (https://github.com/kubernetes/kubernetes/pull/50102/files#r184716591)
|
||||
time.Sleep(1 * time.Second)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,13 +19,22 @@ package watch
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// PreconditionFunc returns true if the condition has been reached, false if it has not been reached yet,
|
||||
// or an error if the condition failed or detected an error state.
|
||||
type PreconditionFunc func(store cache.Store) (bool, error)
|
||||
|
||||
// ConditionFunc returns true if the condition has been reached, false if it has not been reached yet,
|
||||
// or an error if the condition cannot be checked and should terminate. In general, it is better to define
|
||||
// level driven conditions over edge driven conditions (pod has ready=true, vs pod modified and ready changed
|
||||
@@ -86,6 +95,42 @@ func UntilWithoutRetry(ctx context.Context, watcher watch.Interface, conditions
|
||||
return lastEvent, nil
|
||||
}
|
||||
|
||||
// UntilWithSync creates an informer from lw, optionally checks precondition when the store is synced,
|
||||
// and watches the output until each provided condition succeeds, in a way that is identical
|
||||
// to function UntilWithoutRetry. (See above.)
|
||||
// UntilWithSync can deal with all errors like API timeout, lost connections and 'Resource version too old'.
|
||||
// It is the only function that can recover from 'Resource version too old', Until and UntilWithoutRetry will
|
||||
// just fail in that case. On the other hand it can't provide you with guarantees as strong as using simple
|
||||
// Watch method with Until. It can skip some intermediate events in case of watch function failing but it will
|
||||
// re-list to recover and you always get an event, if there has been a change, after recovery.
|
||||
// Also with the current implementation based on DeltaFIFO, order of the events you receive is guaranteed only for
|
||||
// particular object, not between more of them even it's the same resource.
|
||||
// The most frequent usage would be a command that needs to watch the "state of the world" and should't fail, like:
|
||||
// waiting for object reaching a state, "small" controllers, ...
|
||||
func UntilWithSync(ctx context.Context, lw cache.ListerWatcher, objType runtime.Object, precondition PreconditionFunc, conditions ...ConditionFunc) (*watch.Event, error) {
|
||||
indexer, informer, watcher := NewIndexerInformerWatcher(lw, objType)
|
||||
// Proxy watcher can be stopped multiple times so it's fine to use defer here to cover alternative branches and
|
||||
// let UntilWithoutRetry to stop it
|
||||
defer watcher.Stop()
|
||||
|
||||
if precondition != nil {
|
||||
if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
|
||||
return nil, fmt.Errorf("UntilWithSync: unable to sync caches: %v", ctx.Err())
|
||||
}
|
||||
|
||||
done, err := precondition(indexer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if done {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return UntilWithoutRetry(ctx, watcher, conditions...)
|
||||
}
|
||||
|
||||
// ContextWithOptionalTimeout wraps context.WithTimeout and handles infinite timeouts expressed as 0 duration.
|
||||
func ContextWithOptionalTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
if timeout < 0 {
|
||||
@@ -100,3 +145,81 @@ func ContextWithOptionalTimeout(parent context.Context, timeout time.Duration) (
|
||||
|
||||
return context.WithTimeout(parent, timeout)
|
||||
}
|
||||
|
||||
// ListWatchUntil checks the provided conditions against the items returned by the list watcher, returning wait.ErrWaitTimeout
|
||||
// if timeout is exceeded without all conditions returning true, or an error if an error occurs.
|
||||
// TODO: check for watch expired error and retry watch from latest point? Same issue exists for Until.
|
||||
// TODO: remove when no longer used
|
||||
//
|
||||
// Deprecated: Use UntilWithSync instead.
|
||||
func ListWatchUntil(timeout time.Duration, lw cache.ListerWatcher, conditions ...ConditionFunc) (*watch.Event, error) {
|
||||
if len(conditions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
list, err := lw.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initialItems, err := meta.ExtractList(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use the initial items as simulated "adds"
|
||||
var lastEvent *watch.Event
|
||||
currIndex := 0
|
||||
passedConditions := 0
|
||||
for _, condition := range conditions {
|
||||
// check the next condition against the previous event and short circuit waiting for the next watch
|
||||
if lastEvent != nil {
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
passedConditions = passedConditions + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ConditionSucceeded:
|
||||
for currIndex < len(initialItems) {
|
||||
lastEvent = &watch.Event{Type: watch.Added, Object: initialItems[currIndex]}
|
||||
currIndex++
|
||||
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
passedConditions = passedConditions + 1
|
||||
break ConditionSucceeded
|
||||
}
|
||||
}
|
||||
}
|
||||
if passedConditions == len(conditions) {
|
||||
return lastEvent, nil
|
||||
}
|
||||
remainingConditions := conditions[passedConditions:]
|
||||
|
||||
metaObj, err := meta.ListAccessor(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currResourceVersion := metaObj.GetResourceVersion()
|
||||
|
||||
watchInterface, err := lw.Watch(metav1.ListOptions{ResourceVersion: currResourceVersion})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := ContextWithOptionalTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
evt, err := UntilWithoutRetry(ctx, watchInterface, remainingConditions...)
|
||||
if err == ErrWatchClosed {
|
||||
// present a consistent error interface to callers
|
||||
err = wait.ErrWaitTimeout
|
||||
}
|
||||
return evt, err
|
||||
}
|
||||
|
||||
@@ -19,14 +19,19 @@ package watch
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
fakeclient "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type fakePod struct {
|
||||
@@ -172,3 +177,127 @@ func TestUntilErrorCondition(t *testing.T) {
|
||||
t.Fatalf("expected %q in error string, got %q", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntilWithSync(t *testing.T) {
|
||||
// FIXME: test preconditions
|
||||
tt := []struct {
|
||||
name string
|
||||
lw *cache.ListWatch
|
||||
preconditionFunc PreconditionFunc
|
||||
conditionFunc ConditionFunc
|
||||
expectedErr error
|
||||
expectedEvent *watch.Event
|
||||
}{
|
||||
{
|
||||
name: "doesn't wait for sync with no precondition",
|
||||
lw: &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
select {}
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
select {}
|
||||
},
|
||||
},
|
||||
preconditionFunc: nil,
|
||||
conditionFunc: func(e watch.Event) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
expectedErr: errors.New("timed out waiting for the condition"),
|
||||
expectedEvent: nil,
|
||||
},
|
||||
{
|
||||
name: "waits indefinitely with precondition if it can't sync",
|
||||
lw: &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
select {}
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
select {}
|
||||
},
|
||||
},
|
||||
preconditionFunc: func(store cache.Store) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
conditionFunc: func(e watch.Event) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
expectedErr: errors.New("UntilWithSync: unable to sync caches: context deadline exceeded"),
|
||||
expectedEvent: nil,
|
||||
},
|
||||
{
|
||||
name: "precondition can stop the loop",
|
||||
lw: func() *cache.ListWatch {
|
||||
fakeclient := fakeclient.NewSimpleClientset(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}})
|
||||
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return fakeclient.CoreV1().Secrets("").List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return fakeclient.CoreV1().Secrets("").Watch(options)
|
||||
},
|
||||
}
|
||||
}(),
|
||||
preconditionFunc: func(store cache.Store) (bool, error) {
|
||||
_, exists, err := store.Get(&metav1.ObjectMeta{Namespace: "", Name: "first"})
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if exists {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
conditionFunc: func(e watch.Event) (bool, error) {
|
||||
return true, errors.New("should never reach this")
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedEvent: nil,
|
||||
},
|
||||
{
|
||||
name: "precondition lets it proceed to regular condition",
|
||||
lw: func() *cache.ListWatch {
|
||||
fakeclient := fakeclient.NewSimpleClientset(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}})
|
||||
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return fakeclient.CoreV1().Secrets("").List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return fakeclient.CoreV1().Secrets("").Watch(options)
|
||||
},
|
||||
}
|
||||
}(),
|
||||
preconditionFunc: func(store cache.Store) (bool, error) {
|
||||
return false, nil
|
||||
},
|
||||
conditionFunc: func(e watch.Event) (bool, error) {
|
||||
if e.Type == watch.Added {
|
||||
return true, nil
|
||||
}
|
||||
panic("no other events are expected")
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedEvent: &watch.Event{Type: watch.Added, Object: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Informer waits for caches to sync by polling in 100ms intervals,
|
||||
// timeout needs to be reasonably higher
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
event, err := UntilWithSync(ctx, tc.lw, &corev1.Secret{}, tc.preconditionFunc, tc.conditionFunc)
|
||||
|
||||
if !reflect.DeepEqual(err, tc.expectedErr) {
|
||||
t.Errorf("expected error %#v, got %#v", tc.expectedErr, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(event, tc.expectedEvent) {
|
||||
t.Errorf("expected event %#v, got %#v", tc.expectedEvent, event)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ type Config struct {
|
||||
// Bearer token for authentication
|
||||
BearerToken string
|
||||
|
||||
// Path to a file containing a BearerToken.
|
||||
// If set, the contents are periodically read.
|
||||
// The last successfully read value takes precedence over BearerToken.
|
||||
BearerTokenFile string
|
||||
|
||||
// Impersonate is the config that this Config will impersonate using
|
||||
Impersonate ImpersonationConfig
|
||||
|
||||
@@ -80,7 +85,7 @@ func (c *Config) HasBasicAuth() bool {
|
||||
|
||||
// HasTokenAuth returns whether the configuration has token authentication or not.
|
||||
func (c *Config) HasTokenAuth() bool {
|
||||
return len(c.BearerToken) != 0
|
||||
return len(c.BearerToken) != 0 || len(c.BearerTokenFile) != 0
|
||||
}
|
||||
|
||||
// HasCertAuth returns whether the configuration has certificate authentication or not.
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
)
|
||||
@@ -44,7 +45,11 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
|
||||
case config.HasBasicAuth() && config.HasTokenAuth():
|
||||
return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
|
||||
case config.HasTokenAuth():
|
||||
rt = NewBearerAuthRoundTripper(config.BearerToken, rt)
|
||||
var err error
|
||||
rt, err = NewBearerAuthWithRefreshRoundTripper(config.BearerToken, config.BearerTokenFile, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case config.HasBasicAuth():
|
||||
rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
|
||||
}
|
||||
@@ -265,13 +270,35 @@ func (rt *impersonatingRoundTripper) WrappedRoundTripper() http.RoundTripper { r
|
||||
|
||||
type bearerAuthRoundTripper struct {
|
||||
bearer string
|
||||
source oauth2.TokenSource
|
||||
rt http.RoundTripper
|
||||
}
|
||||
|
||||
// NewBearerAuthRoundTripper adds the provided bearer token to a request
|
||||
// unless the authorization header has already been set.
|
||||
func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
|
||||
return &bearerAuthRoundTripper{bearer, rt}
|
||||
return &bearerAuthRoundTripper{bearer, nil, rt}
|
||||
}
|
||||
|
||||
// NewBearerAuthRoundTripper adds the provided bearer token to a request
|
||||
// unless the authorization header has already been set.
|
||||
// If tokenFile is non-empty, it is periodically read,
|
||||
// and the last successfully read content is used as the bearer token.
|
||||
// If tokenFile is non-empty and bearer is empty, the tokenFile is read
|
||||
// immediately to populate the initial bearer token.
|
||||
func NewBearerAuthWithRefreshRoundTripper(bearer string, tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) {
|
||||
if len(tokenFile) == 0 {
|
||||
return &bearerAuthRoundTripper{bearer, nil, rt}, nil
|
||||
}
|
||||
source := NewCachedFileTokenSource(tokenFile)
|
||||
if len(bearer) == 0 {
|
||||
token, err := source.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bearer = token.AccessToken
|
||||
}
|
||||
return &bearerAuthRoundTripper{bearer, source, rt}, nil
|
||||
}
|
||||
|
||||
func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
@@ -280,7 +307,13 @@ func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response,
|
||||
}
|
||||
|
||||
req = utilnet.CloneRequest(req)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.bearer))
|
||||
token := rt.bearer
|
||||
if rt.source != nil {
|
||||
if refreshedToken, err := rt.source.Token(); err == nil {
|
||||
token = refreshedToken.AccessToken
|
||||
}
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
return rt.rt.RoundTrip(req)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ func RoundTripperFor(config *restclient.Config) (http.RoundTripper, Upgrader, er
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true)
|
||||
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true, false)
|
||||
wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
140
transport/token_source.go
Normal file
140
transport/token_source.go
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes 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 transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// TokenSourceWrapTransport returns a WrapTransport that injects bearer tokens
|
||||
// authentication from an oauth2.TokenSource.
|
||||
func TokenSourceWrapTransport(ts oauth2.TokenSource) func(http.RoundTripper) http.RoundTripper {
|
||||
return func(rt http.RoundTripper) http.RoundTripper {
|
||||
return &tokenSourceTransport{
|
||||
base: rt,
|
||||
ort: &oauth2.Transport{
|
||||
Source: ts,
|
||||
Base: rt,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewCachedFileTokenSource returns a oauth2.TokenSource reads a token from a
|
||||
// file at a specified path and periodically reloads it.
|
||||
func NewCachedFileTokenSource(path string) oauth2.TokenSource {
|
||||
return &cachingTokenSource{
|
||||
now: time.Now,
|
||||
leeway: 10 * time.Second,
|
||||
base: &fileTokenSource{
|
||||
path: path,
|
||||
// This period was picked because it is half of the duration between when the kubelet
|
||||
// refreshes a projected service account token and when the original token expires.
|
||||
// Default token lifetime is 10 minutes, and the kubelet starts refreshing at 80% of lifetime.
|
||||
// This should induce re-reading at a frequency that works with the token volume source.
|
||||
period: time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type tokenSourceTransport struct {
|
||||
base http.RoundTripper
|
||||
ort http.RoundTripper
|
||||
}
|
||||
|
||||
func (tst *tokenSourceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// This is to allow --token to override other bearer token providers.
|
||||
if req.Header.Get("Authorization") != "" {
|
||||
return tst.base.RoundTrip(req)
|
||||
}
|
||||
return tst.ort.RoundTrip(req)
|
||||
}
|
||||
|
||||
type fileTokenSource struct {
|
||||
path string
|
||||
period time.Duration
|
||||
}
|
||||
|
||||
var _ = oauth2.TokenSource(&fileTokenSource{})
|
||||
|
||||
func (ts *fileTokenSource) Token() (*oauth2.Token, error) {
|
||||
tokb, err := ioutil.ReadFile(ts.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read token file %q: %v", ts.path, err)
|
||||
}
|
||||
tok := strings.TrimSpace(string(tokb))
|
||||
if len(tok) == 0 {
|
||||
return nil, fmt.Errorf("read empty token from file %q", ts.path)
|
||||
}
|
||||
|
||||
return &oauth2.Token{
|
||||
AccessToken: tok,
|
||||
Expiry: time.Now().Add(ts.period),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type cachingTokenSource struct {
|
||||
base oauth2.TokenSource
|
||||
leeway time.Duration
|
||||
|
||||
sync.RWMutex
|
||||
tok *oauth2.Token
|
||||
|
||||
// for testing
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
var _ = oauth2.TokenSource(&cachingTokenSource{})
|
||||
|
||||
func (ts *cachingTokenSource) Token() (*oauth2.Token, error) {
|
||||
now := ts.now()
|
||||
// fast path
|
||||
ts.RLock()
|
||||
tok := ts.tok
|
||||
ts.RUnlock()
|
||||
|
||||
if tok != nil && tok.Expiry.Add(-1*ts.leeway).After(now) {
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// slow path
|
||||
ts.Lock()
|
||||
defer ts.Unlock()
|
||||
if tok := ts.tok; tok != nil && tok.Expiry.Add(-1*ts.leeway).After(now) {
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
tok, err := ts.base.Token()
|
||||
if err != nil {
|
||||
if ts.tok == nil {
|
||||
return nil, err
|
||||
}
|
||||
glog.Errorf("Unable to rotate token: %v", err)
|
||||
return ts.tok, nil
|
||||
}
|
||||
|
||||
ts.tok = tok
|
||||
return tok, nil
|
||||
}
|
||||
156
transport/token_source_test.go
Normal file
156
transport/token_source_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes 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 transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type testTokenSource struct {
|
||||
calls int
|
||||
tok *oauth2.Token
|
||||
err error
|
||||
}
|
||||
|
||||
func (ts *testTokenSource) Token() (*oauth2.Token, error) {
|
||||
ts.calls++
|
||||
return ts.tok, ts.err
|
||||
}
|
||||
|
||||
func TestCachingTokenSource(t *testing.T) {
|
||||
start := time.Now()
|
||||
tokA := &oauth2.Token{
|
||||
AccessToken: "a",
|
||||
Expiry: start.Add(10 * time.Minute),
|
||||
}
|
||||
tokB := &oauth2.Token{
|
||||
AccessToken: "b",
|
||||
Expiry: start.Add(20 * time.Minute),
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
tok *oauth2.Token
|
||||
tsTok *oauth2.Token
|
||||
tsErr error
|
||||
wait time.Duration
|
||||
|
||||
wantTok *oauth2.Token
|
||||
wantErr bool
|
||||
wantTSCalls int
|
||||
}{
|
||||
{
|
||||
name: "valid token returned from cache",
|
||||
tok: tokA,
|
||||
wantTok: tokA,
|
||||
},
|
||||
{
|
||||
name: "valid token returned from cache 1 minute before scheduled refresh",
|
||||
tok: tokA,
|
||||
wait: 8 * time.Minute,
|
||||
wantTok: tokA,
|
||||
},
|
||||
{
|
||||
name: "new token created when cache is empty",
|
||||
tsTok: tokA,
|
||||
wantTok: tokA,
|
||||
wantTSCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "new token created 1 minute after scheduled refresh",
|
||||
tok: tokA,
|
||||
tsTok: tokB,
|
||||
wait: 10 * time.Minute,
|
||||
wantTok: tokB,
|
||||
wantTSCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "error on create token returns error",
|
||||
tsErr: fmt.Errorf("error"),
|
||||
wantErr: true,
|
||||
wantTSCalls: 1,
|
||||
},
|
||||
}
|
||||
for _, c := range tests {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
tts := &testTokenSource{
|
||||
tok: c.tsTok,
|
||||
err: c.tsErr,
|
||||
}
|
||||
|
||||
ts := &cachingTokenSource{
|
||||
base: tts,
|
||||
tok: c.tok,
|
||||
leeway: 1 * time.Minute,
|
||||
now: func() time.Time { return start.Add(c.wait) },
|
||||
}
|
||||
|
||||
gotTok, gotErr := ts.Token()
|
||||
if got, want := gotTok, c.wantTok; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unexpected token:\n\tgot:\t%#v\n\twant:\t%#v", got, want)
|
||||
}
|
||||
if got, want := tts.calls, c.wantTSCalls; got != want {
|
||||
t.Errorf("unexpected number of Token() calls: got %d, want %d", got, want)
|
||||
}
|
||||
if gotErr == nil && c.wantErr {
|
||||
t.Errorf("wanted error but got none")
|
||||
}
|
||||
if gotErr != nil && !c.wantErr {
|
||||
t.Errorf("unexpected error: %v", gotErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCachingTokenSourceRace(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
tts := &testTokenSource{
|
||||
tok: &oauth2.Token{
|
||||
AccessToken: "a",
|
||||
Expiry: time.Now().Add(1000 * time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
ts := &cachingTokenSource{
|
||||
now: time.Now,
|
||||
base: tts,
|
||||
leeway: 1 * time.Minute,
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(100)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, err := ts.Token(); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if tts.calls != 1 {
|
||||
t.Errorf("expected one call to Token() but saw: %d", tts.calls)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
@@ -87,7 +88,7 @@ func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, er
|
||||
|
||||
// NewSignedCert creates a signed certificate using the given CA certificate and key
|
||||
func NewSignedCert(cfg Config, key *rsa.PrivateKey, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, error) {
|
||||
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -24,10 +24,11 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
certificates "k8s.io/api/certificates/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -38,6 +39,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
@@ -121,7 +123,7 @@ func RequestCertificate(client certificatesclient.CertificateSigningRequestInter
|
||||
func WaitForCertificate(client certificatesclient.CertificateSigningRequestInterface, req *certificates.CertificateSigningRequest, timeout time.Duration) (certData []byte, err error) {
|
||||
fieldSelector := fields.OneTermEqualSelector("metadata.name", req.Name).String()
|
||||
|
||||
event, err := cache.ListWatchUntil(
|
||||
event, err := watchtools.ListWatchUntil(
|
||||
timeout,
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package workqueue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
@@ -24,9 +25,20 @@ import (
|
||||
|
||||
type DoWorkPieceFunc func(piece int)
|
||||
|
||||
// Parallelize is a very simple framework that allow for parallelizing
|
||||
// Parallelize is a very simple framework that allows for parallelizing
|
||||
// N independent pieces of work.
|
||||
func Parallelize(workers, pieces int, doWorkPiece DoWorkPieceFunc) {
|
||||
ParallelizeUntil(nil, workers, pieces, doWorkPiece)
|
||||
}
|
||||
|
||||
// ParallelizeUntil is a framework that allows for parallelizing N
|
||||
// independent pieces of work until done or the context is canceled.
|
||||
func ParallelizeUntil(ctx context.Context, workers, pieces int, doWorkPiece DoWorkPieceFunc) {
|
||||
var stop <-chan struct{}
|
||||
if ctx != nil {
|
||||
stop = ctx.Done()
|
||||
}
|
||||
|
||||
toProcess := make(chan int, pieces)
|
||||
for i := 0; i < pieces; i++ {
|
||||
toProcess <- i
|
||||
@@ -44,7 +56,12 @@ func Parallelize(workers, pieces int, doWorkPiece DoWorkPieceFunc) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer wg.Done()
|
||||
for piece := range toProcess {
|
||||
doWorkPiece(piece)
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
doWorkPiece(piece)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
15
vendor/cloud.google.com/go/AUTHORS
generated
vendored
15
vendor/cloud.google.com/go/AUTHORS
generated
vendored
@@ -1,15 +0,0 @@
|
||||
# This is the official list of cloud authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as:
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Google Inc.
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
Palm Stone Games, Inc.
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
||||
34
vendor/cloud.google.com/go/CONTRIBUTORS
generated
vendored
34
vendor/cloud.google.com/go/CONTRIBUTORS
generated
vendored
@@ -1,34 +0,0 @@
|
||||
# People who have agreed to one of the CLAs and can contribute patches.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# https://developers.google.com/open-source/cla/individual
|
||||
# https://developers.google.com/open-source/cla/corporate
|
||||
#
|
||||
# Names should be added to this file as:
|
||||
# Name <email address>
|
||||
|
||||
# Keep the list alphabetically sorted.
|
||||
|
||||
Andreas Litt <andreas.litt@gmail.com>
|
||||
Andrew Gerrand <adg@golang.org>
|
||||
Brad Fitzpatrick <bradfitz@golang.org>
|
||||
Burcu Dogan <jbd@google.com>
|
||||
Dave Day <djd@golang.org>
|
||||
David Sansome <me@davidsansome.com>
|
||||
David Symonds <dsymonds@golang.org>
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Glenn Lewis <gmlewis@google.com>
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Jonathan Amsterdam <jba@google.com>
|
||||
Luna Duclos <luna.duclos@palmstonegames.com>
|
||||
Michael McGreevy <mcgreevy@golang.org>
|
||||
Omar Jarjur <ojarjur@google.com>
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Sarah Adams <shadams@google.com>
|
||||
Toby Burress <kurin@google.com>
|
||||
Tuo Shan <shantuo@google.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
||||
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
@@ -1,202 +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 2014 Google Inc.
|
||||
|
||||
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.
|
||||
438
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
438
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
@@ -1,438 +0,0 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
|
||||
"cloud.google.com/go/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var (
|
||||
metaClient = &http.Client{
|
||||
Transport: &internal.Transport{
|
||||
Base: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
},
|
||||
},
|
||||
}
|
||||
subscribeClient = &http.Client{
|
||||
Transport: &internal.Transport{
|
||||
Base: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func Get(suffix string) (string, error) {
|
||||
val, _, err := getETag(metaClient, suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated
|
||||
// ETag using the provided client. This func is otherwise equivalent to Get.
|
||||
func getETag(client *http.Client, suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
url := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
func getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cachedValue) get() (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool {
|
||||
onGCEOnce.Do(initOnGCE)
|
||||
return onGCE
|
||||
}
|
||||
|
||||
func initOnGCE() {
|
||||
onGCE = testOnGCE()
|
||||
}
|
||||
|
||||
func testOnGCE() bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194
|
||||
go func() {
|
||||
res, err := ctxhttp.Get(ctx, metaClient, "http://"+metadataIP)
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
addrs, err := net.LookupHost("metadata.google.internal")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}()
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-timer.C:
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false
|
||||
}
|
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||
name := strings.TrimSpace(string(slurp))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := getETag(subscribeClient, suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return projID.get() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return projNum.get() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) {
|
||||
return getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) {
|
||||
return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) {
|
||||
return getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) {
|
||||
return instID.get()
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) {
|
||||
host, err := Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Split(host, ".")[0], nil
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) {
|
||||
zone, err := getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
|
||||
|
||||
func lines(suffix string) ([]string, error) {
|
||||
j, err := Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
64
vendor/cloud.google.com/go/internal/cloud.go
generated
vendored
64
vendor/cloud.google.com/go/internal/cloud.go
generated
vendored
@@ -1,64 +0,0 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 internal provides support for the cloud packages.
|
||||
//
|
||||
// Users should not import this package directly.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const userAgent = "gcloud-golang/0.1"
|
||||
|
||||
// Transport is an http.RoundTripper that appends Google Cloud client's
|
||||
// user-agent to the original request's user-agent header.
|
||||
type Transport struct {
|
||||
// TODO(bradfitz): delete internal.Transport. It's too wrappy for what it does.
|
||||
// Do User-Agent some other way.
|
||||
|
||||
// Base is the actual http.RoundTripper
|
||||
// requests will use. It must not be nil.
|
||||
Base http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip appends a user-agent to the existing user-agent
|
||||
// header and delegates the request to the base http.RoundTripper.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req = cloneRequest(req)
|
||||
ua := req.Header.Get("User-Agent")
|
||||
if ua == "" {
|
||||
ua = userAgent
|
||||
} else {
|
||||
ua = fmt.Sprintf("%s %s", ua, userAgent)
|
||||
}
|
||||
req.Header.Set("User-Agent", ua)
|
||||
return t.Base.RoundTrip(req)
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request.
|
||||
// The clone is a shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
// shallow copy of the struct
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
// deep copy of the Header
|
||||
r2.Header = make(http.Header)
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = s
|
||||
}
|
||||
return r2
|
||||
}
|
||||
191
vendor/github.com/Azure/go-autorest/LICENSE
generated
vendored
191
vendor/github.com/Azure/go-autorest/LICENSE
generated
vendored
@@ -1,191 +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
|
||||
|
||||
Copyright 2015 Microsoft Corporation
|
||||
|
||||
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.
|
||||
292
vendor/github.com/Azure/go-autorest/autorest/adal/README.md
generated
vendored
292
vendor/github.com/Azure/go-autorest/autorest/adal/README.md
generated
vendored
@@ -1,292 +0,0 @@
|
||||
# Azure Active Directory authentication for Go
|
||||
|
||||
This is a standalone package for authenticating with Azure Active
|
||||
Directory from other Go libraries and applications, in particular the [Azure SDK
|
||||
for Go](https://github.com/Azure/azure-sdk-for-go).
|
||||
|
||||
Note: Despite the package's name it is not related to other "ADAL" libraries
|
||||
maintained in the [github.com/AzureAD](https://github.com/AzureAD) org. Issues
|
||||
should be opened in [this repo's](https://github.com/Azure/go-autorest/issues)
|
||||
or [the SDK's](https://github.com/Azure/azure-sdk-for-go/issues) issue
|
||||
trackers.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get -u github.com/Azure/go-autorest/autorest/adal
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
An Active Directory application is required in order to use this library. An application can be registered in the [Azure Portal](https://portal.azure.com/) by following these [guidelines](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-integrating-applications) or using the [Azure CLI](https://github.com/Azure/azure-cli).
|
||||
|
||||
### Register an Azure AD Application with secret
|
||||
|
||||
|
||||
1. Register a new application with a `secret` credential
|
||||
|
||||
```
|
||||
az ad app create \
|
||||
--display-name example-app \
|
||||
--homepage https://example-app/home \
|
||||
--identifier-uris https://example-app/app \
|
||||
--password secret
|
||||
```
|
||||
|
||||
2. Create a service principal using the `Application ID` from previous step
|
||||
|
||||
```
|
||||
az ad sp create --id "Application ID"
|
||||
```
|
||||
|
||||
* Replace `Application ID` with `appId` from step 1.
|
||||
|
||||
### Register an Azure AD Application with certificate
|
||||
|
||||
1. Create a private key
|
||||
|
||||
```
|
||||
openssl genrsa -out "example-app.key" 2048
|
||||
```
|
||||
|
||||
2. Create the certificate
|
||||
|
||||
```
|
||||
openssl req -new -key "example-app.key" -subj "/CN=example-app" -out "example-app.csr"
|
||||
openssl x509 -req -in "example-app.csr" -signkey "example-app.key" -out "example-app.crt" -days 10000
|
||||
```
|
||||
|
||||
3. Create the PKCS12 version of the certificate containing also the private key
|
||||
|
||||
```
|
||||
openssl pkcs12 -export -out "example-app.pfx" -inkey "example-app.key" -in "example-app.crt" -passout pass:
|
||||
|
||||
```
|
||||
|
||||
4. Register a new application with the certificate content form `example-app.crt`
|
||||
|
||||
```
|
||||
certificateContents="$(tail -n+2 "example-app.crt" | head -n-1)"
|
||||
|
||||
az ad app create \
|
||||
--display-name example-app \
|
||||
--homepage https://example-app/home \
|
||||
--identifier-uris https://example-app/app \
|
||||
--key-usage Verify --end-date 2018-01-01 \
|
||||
--key-value "${certificateContents}"
|
||||
```
|
||||
|
||||
5. Create a service principal using the `Application ID` from previous step
|
||||
|
||||
```
|
||||
az ad sp create --id "APPLICATION_ID"
|
||||
```
|
||||
|
||||
* Replace `APPLICATION_ID` with `appId` from step 4.
|
||||
|
||||
|
||||
### Grant the necessary permissions
|
||||
|
||||
Azure relies on a Role-Based Access Control (RBAC) model to manage the access to resources at a fine-grained
|
||||
level. There is a set of [pre-defined roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-built-in-roles)
|
||||
which can be assigned to a service principal of an Azure AD application depending of your needs.
|
||||
|
||||
```
|
||||
az role assignment create --assigner "SERVICE_PRINCIPAL_ID" --role "ROLE_NAME"
|
||||
```
|
||||
|
||||
* Replace the `SERVICE_PRINCIPAL_ID` with the `appId` from previous step.
|
||||
* Replace the `ROLE_NAME` with a role name of your choice.
|
||||
|
||||
It is also possible to define custom role definitions.
|
||||
|
||||
```
|
||||
az role definition create --role-definition role-definition.json
|
||||
```
|
||||
|
||||
* Check [custom roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-control-custom-roles) for more details regarding the content of `role-definition.json` file.
|
||||
|
||||
|
||||
### Acquire Access Token
|
||||
|
||||
The common configuration used by all flows:
|
||||
|
||||
```Go
|
||||
const activeDirectoryEndpoint = "https://login.microsoftonline.com/"
|
||||
tenantID := "TENANT_ID"
|
||||
oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, tenantID)
|
||||
|
||||
applicationID := "APPLICATION_ID"
|
||||
|
||||
callback := func(token adal.Token) error {
|
||||
// This is called after the token is acquired
|
||||
}
|
||||
|
||||
// The resource for which the token is acquired
|
||||
resource := "https://management.core.windows.net/"
|
||||
```
|
||||
|
||||
* Replace the `TENANT_ID` with your tenant ID.
|
||||
* Replace the `APPLICATION_ID` with the value from previous section.
|
||||
|
||||
#### Client Credentials
|
||||
|
||||
```Go
|
||||
applicationSecret := "APPLICATION_SECRET"
|
||||
|
||||
spt, err := adal.NewServicePrincipalToken(
|
||||
oauthConfig,
|
||||
appliationID,
|
||||
applicationSecret,
|
||||
resource,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Acquire a new access token
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
* Replace the `APPLICATION_SECRET` with the `password` value from previous section.
|
||||
|
||||
#### Client Certificate
|
||||
|
||||
```Go
|
||||
certificatePath := "./example-app.pfx"
|
||||
|
||||
certData, err := ioutil.ReadFile(certificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err)
|
||||
}
|
||||
|
||||
// Get the certificate and private key from pfx file
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromCertificate(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
certificate,
|
||||
rsaPrivateKey,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
// Acquire a new access token
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
* Update the certificate path to point to the example-app.pfx file which was created in previous section.
|
||||
|
||||
|
||||
#### Device Code
|
||||
|
||||
```Go
|
||||
oauthClient := &http.Client{}
|
||||
|
||||
// Acquire the device code
|
||||
deviceCode, err := adal.InitiateDeviceAuth(
|
||||
oauthClient,
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
// Display the authentication message
|
||||
fmt.Println(*deviceCode.Message)
|
||||
|
||||
// Wait here until the user is authenticated
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromManualToken(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
#### Username password authenticate
|
||||
|
||||
```Go
|
||||
spt, err := adal.NewServicePrincipalTokenFromUsernamePassword(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
username,
|
||||
password,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
#### Authorization code authenticate
|
||||
|
||||
``` Go
|
||||
spt, err := adal.NewServicePrincipalTokenFromAuthorizationCode(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
clientSecret,
|
||||
authorizationCode,
|
||||
redirectURI,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
### Command Line Tool
|
||||
|
||||
A command line tool is available in `cmd/adal.go` that can acquire a token for a given resource. It supports all flows mentioned above.
|
||||
|
||||
```
|
||||
adal -h
|
||||
|
||||
Usage of ./adal:
|
||||
-applicationId string
|
||||
application id
|
||||
-certificatePath string
|
||||
path to pk12/PFC application certificate
|
||||
-mode string
|
||||
authentication mode (device, secret, cert, refresh) (default "device")
|
||||
-resource string
|
||||
resource for which the token is requested
|
||||
-secret string
|
||||
application secret
|
||||
-tenantId string
|
||||
tenant id
|
||||
-tokenCachePath string
|
||||
location of oath token cache (default "/home/cgc/.adal/accessToken.json")
|
||||
```
|
||||
|
||||
Example acquire a token for `https://management.core.windows.net/` using device code flow:
|
||||
|
||||
```
|
||||
adal -mode device \
|
||||
-applicationId "APPLICATION_ID" \
|
||||
-tenantId "TENANT_ID" \
|
||||
-resource https://management.core.windows.net/
|
||||
|
||||
```
|
||||
81
vendor/github.com/Azure/go-autorest/autorest/adal/config.go
generated
vendored
81
vendor/github.com/Azure/go-autorest/autorest/adal/config.go
generated
vendored
@@ -1,81 +0,0 @@
|
||||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
activeDirectoryAPIVersion = "1.0"
|
||||
)
|
||||
|
||||
// OAuthConfig represents the endpoints needed
|
||||
// in OAuth operations
|
||||
type OAuthConfig struct {
|
||||
AuthorityEndpoint url.URL `json:"authorityEndpoint"`
|
||||
AuthorizeEndpoint url.URL `json:"authorizeEndpoint"`
|
||||
TokenEndpoint url.URL `json:"tokenEndpoint"`
|
||||
DeviceCodeEndpoint url.URL `json:"deviceCodeEndpoint"`
|
||||
}
|
||||
|
||||
// IsZero returns true if the OAuthConfig object is zero-initialized.
|
||||
func (oac OAuthConfig) IsZero() bool {
|
||||
return oac == OAuthConfig{}
|
||||
}
|
||||
|
||||
func validateStringParam(param, name string) error {
|
||||
if len(param) == 0 {
|
||||
return fmt.Errorf("parameter '" + name + "' cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewOAuthConfig returns an OAuthConfig with tenant specific urls
|
||||
func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) {
|
||||
if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// it's legal for tenantID to be empty so don't validate it
|
||||
const activeDirectoryEndpointTemplate = "%s/oauth2/%s?api-version=%s"
|
||||
u, err := url.Parse(activeDirectoryEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorityURL, err := u.Parse(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorizeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "authorize", activeDirectoryAPIVersion))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "token", activeDirectoryAPIVersion))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deviceCodeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "devicecode", activeDirectoryAPIVersion))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &OAuthConfig{
|
||||
AuthorityEndpoint: *authorityURL,
|
||||
AuthorizeEndpoint: *authorizeURL,
|
||||
TokenEndpoint: *tokenURL,
|
||||
DeviceCodeEndpoint: *deviceCodeURL,
|
||||
}, nil
|
||||
}
|
||||
242
vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go
generated
vendored
242
vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go
generated
vendored
@@ -1,242 +0,0 @@
|
||||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
/*
|
||||
This file is largely based on rjw57/oauth2device's code, with the follow differences:
|
||||
* scope -> resource, and only allow a single one
|
||||
* receive "Message" in the DeviceCode struct and show it to users as the prompt
|
||||
* azure-xplat-cli has the following behavior that this emulates:
|
||||
- does not send client_secret during the token exchange
|
||||
- sends resource again in the token exchange request
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
logPrefix = "autorest/adal/devicetoken:"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDeviceGeneric represents an unknown error from the token endpoint when using device flow
|
||||
ErrDeviceGeneric = fmt.Errorf("%s Error while retrieving OAuth token: Unknown Error", logPrefix)
|
||||
|
||||
// ErrDeviceAccessDenied represents an access denied error from the token endpoint when using device flow
|
||||
ErrDeviceAccessDenied = fmt.Errorf("%s Error while retrieving OAuth token: Access Denied", logPrefix)
|
||||
|
||||
// ErrDeviceAuthorizationPending represents the server waiting on the user to complete the device flow
|
||||
ErrDeviceAuthorizationPending = fmt.Errorf("%s Error while retrieving OAuth token: Authorization Pending", logPrefix)
|
||||
|
||||
// ErrDeviceCodeExpired represents the server timing out and expiring the code during device flow
|
||||
ErrDeviceCodeExpired = fmt.Errorf("%s Error while retrieving OAuth token: Code Expired", logPrefix)
|
||||
|
||||
// ErrDeviceSlowDown represents the service telling us we're polling too often during device flow
|
||||
ErrDeviceSlowDown = fmt.Errorf("%s Error while retrieving OAuth token: Slow Down", logPrefix)
|
||||
|
||||
// ErrDeviceCodeEmpty represents an empty device code from the device endpoint while using device flow
|
||||
ErrDeviceCodeEmpty = fmt.Errorf("%s Error while retrieving device code: Device Code Empty", logPrefix)
|
||||
|
||||
// ErrOAuthTokenEmpty represents an empty OAuth token from the token endpoint when using device flow
|
||||
ErrOAuthTokenEmpty = fmt.Errorf("%s Error while retrieving OAuth token: Token Empty", logPrefix)
|
||||
|
||||
errCodeSendingFails = "Error occurred while sending request for Device Authorization Code"
|
||||
errCodeHandlingFails = "Error occurred while handling response from the Device Endpoint"
|
||||
errTokenSendingFails = "Error occurred while sending request with device code for a token"
|
||||
errTokenHandlingFails = "Error occurred while handling response from the Token Endpoint (during device flow)"
|
||||
errStatusNotOK = "Error HTTP status != 200"
|
||||
)
|
||||
|
||||
// DeviceCode is the object returned by the device auth endpoint
|
||||
// It contains information to instruct the user to complete the auth flow
|
||||
type DeviceCode struct {
|
||||
DeviceCode *string `json:"device_code,omitempty"`
|
||||
UserCode *string `json:"user_code,omitempty"`
|
||||
VerificationURL *string `json:"verification_url,omitempty"`
|
||||
ExpiresIn *int64 `json:"expires_in,string,omitempty"`
|
||||
Interval *int64 `json:"interval,string,omitempty"`
|
||||
|
||||
Message *string `json:"message"` // Azure specific
|
||||
Resource string // store the following, stored when initiating, used when exchanging
|
||||
OAuthConfig OAuthConfig
|
||||
ClientID string
|
||||
}
|
||||
|
||||
// TokenError is the object returned by the token exchange endpoint
|
||||
// when something is amiss
|
||||
type TokenError struct {
|
||||
Error *string `json:"error,omitempty"`
|
||||
ErrorCodes []int `json:"error_codes,omitempty"`
|
||||
ErrorDescription *string `json:"error_description,omitempty"`
|
||||
Timestamp *string `json:"timestamp,omitempty"`
|
||||
TraceID *string `json:"trace_id,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceToken is the object return by the token exchange endpoint
|
||||
// It can either look like a Token or an ErrorToken, so put both here
|
||||
// and check for presence of "Error" to know if we are in error state
|
||||
type deviceToken struct {
|
||||
Token
|
||||
TokenError
|
||||
}
|
||||
|
||||
// InitiateDeviceAuth initiates a device auth flow. It returns a DeviceCode
|
||||
// that can be used with CheckForUserCompletion or WaitForUserCompletion.
|
||||
func InitiateDeviceAuth(sender Sender, oauthConfig OAuthConfig, clientID, resource string) (*DeviceCode, error) {
|
||||
v := url.Values{
|
||||
"client_id": []string{clientID},
|
||||
"resource": []string{resource},
|
||||
}
|
||||
|
||||
s := v.Encode()
|
||||
body := ioutil.NopCloser(strings.NewReader(s))
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, oauthConfig.DeviceCodeEndpoint.String(), body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err.Error())
|
||||
}
|
||||
|
||||
req.ContentLength = int64(len(s))
|
||||
req.Header.Set(contentType, mimeTypeFormPost)
|
||||
resp, err := sender.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
rb, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, errStatusNotOK)
|
||||
}
|
||||
|
||||
if len(strings.Trim(string(rb), " ")) == 0 {
|
||||
return nil, ErrDeviceCodeEmpty
|
||||
}
|
||||
|
||||
var code DeviceCode
|
||||
err = json.Unmarshal(rb, &code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
code.ClientID = clientID
|
||||
code.Resource = resource
|
||||
code.OAuthConfig = oauthConfig
|
||||
|
||||
return &code, nil
|
||||
}
|
||||
|
||||
// CheckForUserCompletion takes a DeviceCode and checks with the Azure AD OAuth endpoint
|
||||
// to see if the device flow has: been completed, timed out, or otherwise failed
|
||||
func CheckForUserCompletion(sender Sender, code *DeviceCode) (*Token, error) {
|
||||
v := url.Values{
|
||||
"client_id": []string{code.ClientID},
|
||||
"code": []string{*code.DeviceCode},
|
||||
"grant_type": []string{OAuthGrantTypeDeviceCode},
|
||||
"resource": []string{code.Resource},
|
||||
}
|
||||
|
||||
s := v.Encode()
|
||||
body := ioutil.NopCloser(strings.NewReader(s))
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, code.OAuthConfig.TokenEndpoint.String(), body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err.Error())
|
||||
}
|
||||
|
||||
req.ContentLength = int64(len(s))
|
||||
req.Header.Set(contentType, mimeTypeFormPost)
|
||||
resp, err := sender.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
rb, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK && len(strings.Trim(string(rb), " ")) == 0 {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, errStatusNotOK)
|
||||
}
|
||||
if len(strings.Trim(string(rb), " ")) == 0 {
|
||||
return nil, ErrOAuthTokenEmpty
|
||||
}
|
||||
|
||||
var token deviceToken
|
||||
err = json.Unmarshal(rb, &token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
if token.Error == nil {
|
||||
return &token.Token, nil
|
||||
}
|
||||
|
||||
switch *token.Error {
|
||||
case "authorization_pending":
|
||||
return nil, ErrDeviceAuthorizationPending
|
||||
case "slow_down":
|
||||
return nil, ErrDeviceSlowDown
|
||||
case "access_denied":
|
||||
return nil, ErrDeviceAccessDenied
|
||||
case "code_expired":
|
||||
return nil, ErrDeviceCodeExpired
|
||||
default:
|
||||
return nil, ErrDeviceGeneric
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForUserCompletion calls CheckForUserCompletion repeatedly until a token is granted or an error state occurs.
|
||||
// This prevents the user from looping and checking against 'ErrDeviceAuthorizationPending'.
|
||||
func WaitForUserCompletion(sender Sender, code *DeviceCode) (*Token, error) {
|
||||
intervalDuration := time.Duration(*code.Interval) * time.Second
|
||||
waitDuration := intervalDuration
|
||||
|
||||
for {
|
||||
token, err := CheckForUserCompletion(sender, code)
|
||||
|
||||
if err == nil {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
switch err {
|
||||
case ErrDeviceSlowDown:
|
||||
waitDuration += waitDuration
|
||||
case ErrDeviceAuthorizationPending:
|
||||
// noop
|
||||
default: // everything else is "fatal" to us
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if waitDuration > (intervalDuration * 3) {
|
||||
return nil, fmt.Errorf("%s Error waiting for user to complete device flow. Server told us to slow_down too much", logPrefix)
|
||||
}
|
||||
|
||||
time.Sleep(waitDuration)
|
||||
}
|
||||
}
|
||||
73
vendor/github.com/Azure/go-autorest/autorest/adal/persist.go
generated
vendored
73
vendor/github.com/Azure/go-autorest/autorest/adal/persist.go
generated
vendored
@@ -1,73 +0,0 @@
|
||||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// LoadToken restores a Token object from a file located at 'path'.
|
||||
func LoadToken(path string) (*Token, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file (%s) while loading token: %v", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var token Token
|
||||
|
||||
dec := json.NewDecoder(file)
|
||||
if err = dec.Decode(&token); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode contents of file (%s) into Token representation: %v", path, err)
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// SaveToken persists an oauth token at the given location on disk.
|
||||
// It moves the new file into place so it can safely be used to replace an existing file
|
||||
// that maybe accessed by multiple processes.
|
||||
func SaveToken(path string, mode os.FileMode, token Token) error {
|
||||
dir := filepath.Dir(path)
|
||||
err := os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory (%s) to store token in: %v", dir, err)
|
||||
}
|
||||
|
||||
newFile, err := ioutil.TempFile(dir, "token")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the temp file to write the token: %v", err)
|
||||
}
|
||||
tempPath := newFile.Name()
|
||||
|
||||
if err := json.NewEncoder(newFile).Encode(token); err != nil {
|
||||
return fmt.Errorf("failed to encode token to file (%s) while saving token: %v", tempPath, err)
|
||||
}
|
||||
if err := newFile.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close temp file %s: %v", tempPath, err)
|
||||
}
|
||||
|
||||
// Atomic replace to avoid multi-writer file corruptions
|
||||
if err := os.Rename(tempPath, path); err != nil {
|
||||
return fmt.Errorf("failed to move temporary token to desired output location. src=%s dst=%s: %v", tempPath, path, err)
|
||||
}
|
||||
if err := os.Chmod(path, mode); err != nil {
|
||||
return fmt.Errorf("failed to chmod the token file %s: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
60
vendor/github.com/Azure/go-autorest/autorest/adal/sender.go
generated
vendored
60
vendor/github.com/Azure/go-autorest/autorest/adal/sender.go
generated
vendored
@@ -1,60 +0,0 @@
|
||||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
contentType = "Content-Type"
|
||||
mimeTypeFormPost = "application/x-www-form-urlencoded"
|
||||
)
|
||||
|
||||
// Sender is the interface that wraps the Do method to send HTTP requests.
|
||||
//
|
||||
// The standard http.Client conforms to this interface.
|
||||
type Sender interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// SenderFunc is a method that implements the Sender interface.
|
||||
type SenderFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
// Do implements the Sender interface on SenderFunc.
|
||||
func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) {
|
||||
return sf(r)
|
||||
}
|
||||
|
||||
// SendDecorator takes and possibily decorates, by wrapping, a Sender. Decorators may affect the
|
||||
// http.Request and pass it along or, first, pass the http.Request along then react to the
|
||||
// http.Response result.
|
||||
type SendDecorator func(Sender) Sender
|
||||
|
||||
// CreateSender creates, decorates, and returns, as a Sender, the default http.Client.
|
||||
func CreateSender(decorators ...SendDecorator) Sender {
|
||||
return DecorateSender(&http.Client{}, decorators...)
|
||||
}
|
||||
|
||||
// DecorateSender accepts a Sender and a, possibly empty, set of SendDecorators, which is applies to
|
||||
// the Sender. Decorators are applied in the order received, but their affect upon the request
|
||||
// depends on whether they are a pre-decorator (change the http.Request and then pass it along) or a
|
||||
// post-decorator (pass the http.Request along and react to the results in http.Response).
|
||||
func DecorateSender(s Sender, decorators ...SendDecorator) Sender {
|
||||
for _, decorate := range decorators {
|
||||
s = decorate(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
968
vendor/github.com/Azure/go-autorest/autorest/adal/token.go
generated
vendored
968
vendor/github.com/Azure/go-autorest/autorest/adal/token.go
generated
vendored
@@ -1,968 +0,0 @@
|
||||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/date"
|
||||
"github.com/Azure/go-autorest/version"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRefresh = 5 * time.Minute
|
||||
|
||||
// OAuthGrantTypeDeviceCode is the "grant_type" identifier used in device flow
|
||||
OAuthGrantTypeDeviceCode = "device_code"
|
||||
|
||||
// OAuthGrantTypeClientCredentials is the "grant_type" identifier used in credential flows
|
||||
OAuthGrantTypeClientCredentials = "client_credentials"
|
||||
|
||||
// OAuthGrantTypeUserPass is the "grant_type" identifier used in username and password auth flows
|
||||
OAuthGrantTypeUserPass = "password"
|
||||
|
||||
// OAuthGrantTypeRefreshToken is the "grant_type" identifier used in refresh token flows
|
||||
OAuthGrantTypeRefreshToken = "refresh_token"
|
||||
|
||||
// OAuthGrantTypeAuthorizationCode is the "grant_type" identifier used in authorization code flows
|
||||
OAuthGrantTypeAuthorizationCode = "authorization_code"
|
||||
|
||||
// metadataHeader is the header required by MSI extension
|
||||
metadataHeader = "Metadata"
|
||||
|
||||
// msiEndpoint is the well known endpoint for getting MSI authentications tokens
|
||||
msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
|
||||
|
||||
// the default number of attempts to refresh an MSI authentication token
|
||||
defaultMaxMSIRefreshAttempts = 5
|
||||
)
|
||||
|
||||
// OAuthTokenProvider is an interface which should be implemented by an access token retriever
|
||||
type OAuthTokenProvider interface {
|
||||
OAuthToken() string
|
||||
}
|
||||
|
||||
// TokenRefreshError is an interface used by errors returned during token refresh.
|
||||
type TokenRefreshError interface {
|
||||
error
|
||||
Response() *http.Response
|
||||
}
|
||||
|
||||
// Refresher is an interface for token refresh functionality
|
||||
type Refresher interface {
|
||||
Refresh() error
|
||||
RefreshExchange(resource string) error
|
||||
EnsureFresh() error
|
||||
}
|
||||
|
||||
// RefresherWithContext is an interface for token refresh functionality
|
||||
type RefresherWithContext interface {
|
||||
RefreshWithContext(ctx context.Context) error
|
||||
RefreshExchangeWithContext(ctx context.Context, resource string) error
|
||||
EnsureFreshWithContext(ctx context.Context) error
|
||||
}
|
||||
|
||||
// TokenRefreshCallback is the type representing callbacks that will be called after
|
||||
// a successful token refresh
|
||||
type TokenRefreshCallback func(Token) error
|
||||
|
||||
// Token encapsulates the access token used to authorize Azure requests.
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
|
||||
ExpiresIn string `json:"expires_in"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
NotBefore string `json:"not_before"`
|
||||
|
||||
Resource string `json:"resource"`
|
||||
Type string `json:"token_type"`
|
||||
}
|
||||
|
||||
// IsZero returns true if the token object is zero-initialized.
|
||||
func (t Token) IsZero() bool {
|
||||
return t == Token{}
|
||||
}
|
||||
|
||||
// Expires returns the time.Time when the Token expires.
|
||||
func (t Token) Expires() time.Time {
|
||||
s, err := strconv.Atoi(t.ExpiresOn)
|
||||
if err != nil {
|
||||
s = -3600
|
||||
}
|
||||
|
||||
expiration := date.NewUnixTimeFromSeconds(float64(s))
|
||||
|
||||
return time.Time(expiration).UTC()
|
||||
}
|
||||
|
||||
// IsExpired returns true if the Token is expired, false otherwise.
|
||||
func (t Token) IsExpired() bool {
|
||||
return t.WillExpireIn(0)
|
||||
}
|
||||
|
||||
// WillExpireIn returns true if the Token will expire after the passed time.Duration interval
|
||||
// from now, false otherwise.
|
||||
func (t Token) WillExpireIn(d time.Duration) bool {
|
||||
return !t.Expires().After(time.Now().Add(d))
|
||||
}
|
||||
|
||||
//OAuthToken return the current access token
|
||||
func (t *Token) OAuthToken() string {
|
||||
return t.AccessToken
|
||||
}
|
||||
|
||||
// ServicePrincipalSecret is an interface that allows various secret mechanism to fill the form
|
||||
// that is submitted when acquiring an oAuth token.
|
||||
type ServicePrincipalSecret interface {
|
||||
SetAuthenticationValues(spt *ServicePrincipalToken, values *url.Values) error
|
||||
}
|
||||
|
||||
// ServicePrincipalNoSecret represents a secret type that contains no secret
|
||||
// meaning it is not valid for fetching a fresh token. This is used by Manual
|
||||
type ServicePrincipalNoSecret struct {
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret
|
||||
// It only returns an error for the ServicePrincipalNoSecret type
|
||||
func (noSecret *ServicePrincipalNoSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
return fmt.Errorf("Manually created ServicePrincipalToken does not contain secret material to retrieve a new access token")
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (noSecret ServicePrincipalNoSecret) MarshalJSON() ([]byte, error) {
|
||||
type tokenType struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
return json.Marshal(tokenType{
|
||||
Type: "ServicePrincipalNoSecret",
|
||||
})
|
||||
}
|
||||
|
||||
// ServicePrincipalTokenSecret implements ServicePrincipalSecret for client_secret type authorization.
|
||||
type ServicePrincipalTokenSecret struct {
|
||||
ClientSecret string `json:"value"`
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
// It will populate the form submitted during oAuth Token Acquisition using the client_secret.
|
||||
func (tokenSecret *ServicePrincipalTokenSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("client_secret", tokenSecret.ClientSecret)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (tokenSecret ServicePrincipalTokenSecret) MarshalJSON() ([]byte, error) {
|
||||
type tokenType struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
return json.Marshal(tokenType{
|
||||
Type: "ServicePrincipalTokenSecret",
|
||||
Value: tokenSecret.ClientSecret,
|
||||
})
|
||||
}
|
||||
|
||||
// ServicePrincipalCertificateSecret implements ServicePrincipalSecret for generic RSA cert auth with signed JWTs.
|
||||
type ServicePrincipalCertificateSecret struct {
|
||||
Certificate *x509.Certificate
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
// SignJwt returns the JWT signed with the certificate's private key.
|
||||
func (secret *ServicePrincipalCertificateSecret) SignJwt(spt *ServicePrincipalToken) (string, error) {
|
||||
hasher := sha1.New()
|
||||
_, err := hasher.Write(secret.Certificate.Raw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
thumbprint := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
// The jti (JWT ID) claim provides a unique identifier for the JWT.
|
||||
jti := make([]byte, 20)
|
||||
_, err = rand.Read(jti)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := jwt.New(jwt.SigningMethodRS256)
|
||||
token.Header["x5t"] = thumbprint
|
||||
token.Claims = jwt.MapClaims{
|
||||
"aud": spt.inner.OauthConfig.TokenEndpoint.String(),
|
||||
"iss": spt.inner.ClientID,
|
||||
"sub": spt.inner.ClientID,
|
||||
"jti": base64.URLEncoding.EncodeToString(jti),
|
||||
"nbf": time.Now().Unix(),
|
||||
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
||||
}
|
||||
|
||||
signedString, err := token.SignedString(secret.PrivateKey)
|
||||
return signedString, err
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
// It will populate the form submitted during oAuth Token Acquisition using a JWT signed with a certificate.
|
||||
func (secret *ServicePrincipalCertificateSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
jwt, err := secret.SignJwt(spt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.Set("client_assertion", jwt)
|
||||
v.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (secret ServicePrincipalCertificateSecret) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("marshalling ServicePrincipalCertificateSecret is not supported")
|
||||
}
|
||||
|
||||
// ServicePrincipalMSISecret implements ServicePrincipalSecret for machines running the MSI Extension.
|
||||
type ServicePrincipalMSISecret struct {
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (msiSecret ServicePrincipalMSISecret) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("marshalling ServicePrincipalMSISecret is not supported")
|
||||
}
|
||||
|
||||
// ServicePrincipalUsernamePasswordSecret implements ServicePrincipalSecret for username and password auth.
|
||||
type ServicePrincipalUsernamePasswordSecret struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (secret *ServicePrincipalUsernamePasswordSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("username", secret.Username)
|
||||
v.Set("password", secret.Password)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (secret ServicePrincipalUsernamePasswordSecret) MarshalJSON() ([]byte, error) {
|
||||
type tokenType struct {
|
||||
Type string `json:"type"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
return json.Marshal(tokenType{
|
||||
Type: "ServicePrincipalUsernamePasswordSecret",
|
||||
Username: secret.Username,
|
||||
Password: secret.Password,
|
||||
})
|
||||
}
|
||||
|
||||
// ServicePrincipalAuthorizationCodeSecret implements ServicePrincipalSecret for authorization code auth.
|
||||
type ServicePrincipalAuthorizationCodeSecret struct {
|
||||
ClientSecret string `json:"value"`
|
||||
AuthorizationCode string `json:"authCode"`
|
||||
RedirectURI string `json:"redirect"`
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (secret *ServicePrincipalAuthorizationCodeSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("code", secret.AuthorizationCode)
|
||||
v.Set("client_secret", secret.ClientSecret)
|
||||
v.Set("redirect_uri", secret.RedirectURI)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (secret ServicePrincipalAuthorizationCodeSecret) MarshalJSON() ([]byte, error) {
|
||||
type tokenType struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
AuthCode string `json:"authCode"`
|
||||
Redirect string `json:"redirect"`
|
||||
}
|
||||
return json.Marshal(tokenType{
|
||||
Type: "ServicePrincipalAuthorizationCodeSecret",
|
||||
Value: secret.ClientSecret,
|
||||
AuthCode: secret.AuthorizationCode,
|
||||
Redirect: secret.RedirectURI,
|
||||
})
|
||||
}
|
||||
|
||||
// ServicePrincipalToken encapsulates a Token created for a Service Principal.
|
||||
type ServicePrincipalToken struct {
|
||||
inner servicePrincipalToken
|
||||
refreshLock *sync.RWMutex
|
||||
sender Sender
|
||||
refreshCallbacks []TokenRefreshCallback
|
||||
// MaxMSIRefreshAttempts is the maximum number of attempts to refresh an MSI token.
|
||||
MaxMSIRefreshAttempts int
|
||||
}
|
||||
|
||||
// MarshalTokenJSON returns the marshalled inner token.
|
||||
func (spt ServicePrincipalToken) MarshalTokenJSON() ([]byte, error) {
|
||||
return json.Marshal(spt.inner.Token)
|
||||
}
|
||||
|
||||
// SetRefreshCallbacks replaces any existing refresh callbacks with the specified callbacks.
|
||||
func (spt *ServicePrincipalToken) SetRefreshCallbacks(callbacks []TokenRefreshCallback) {
|
||||
spt.refreshCallbacks = callbacks
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (spt ServicePrincipalToken) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(spt.inner)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (spt *ServicePrincipalToken) UnmarshalJSON(data []byte) error {
|
||||
// need to determine the token type
|
||||
raw := map[string]interface{}{}
|
||||
err := json.Unmarshal(data, &raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secret := raw["secret"].(map[string]interface{})
|
||||
switch secret["type"] {
|
||||
case "ServicePrincipalNoSecret":
|
||||
spt.inner.Secret = &ServicePrincipalNoSecret{}
|
||||
case "ServicePrincipalTokenSecret":
|
||||
spt.inner.Secret = &ServicePrincipalTokenSecret{}
|
||||
case "ServicePrincipalCertificateSecret":
|
||||
return errors.New("unmarshalling ServicePrincipalCertificateSecret is not supported")
|
||||
case "ServicePrincipalMSISecret":
|
||||
return errors.New("unmarshalling ServicePrincipalMSISecret is not supported")
|
||||
case "ServicePrincipalUsernamePasswordSecret":
|
||||
spt.inner.Secret = &ServicePrincipalUsernamePasswordSecret{}
|
||||
case "ServicePrincipalAuthorizationCodeSecret":
|
||||
spt.inner.Secret = &ServicePrincipalAuthorizationCodeSecret{}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized token type '%s'", secret["type"])
|
||||
}
|
||||
err = json.Unmarshal(data, &spt.inner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spt.refreshLock = &sync.RWMutex{}
|
||||
spt.sender = &http.Client{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// internal type used for marshalling/unmarshalling
|
||||
type servicePrincipalToken struct {
|
||||
Token Token `json:"token"`
|
||||
Secret ServicePrincipalSecret `json:"secret"`
|
||||
OauthConfig OAuthConfig `json:"oauth"`
|
||||
ClientID string `json:"clientID"`
|
||||
Resource string `json:"resource"`
|
||||
AutoRefresh bool `json:"autoRefresh"`
|
||||
RefreshWithin time.Duration `json:"refreshWithin"`
|
||||
}
|
||||
|
||||
func validateOAuthConfig(oac OAuthConfig) error {
|
||||
if oac.IsZero() {
|
||||
return fmt.Errorf("parameter 'oauthConfig' cannot be zero-initialized")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenWithSecret create a ServicePrincipalToken using the supplied ServicePrincipalSecret implementation.
|
||||
func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, resource string, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(id, "id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("parameter 'secret' cannot be nil")
|
||||
}
|
||||
spt := &ServicePrincipalToken{
|
||||
inner: servicePrincipalToken{
|
||||
OauthConfig: oauthConfig,
|
||||
Secret: secret,
|
||||
ClientID: id,
|
||||
Resource: resource,
|
||||
AutoRefresh: true,
|
||||
RefreshWithin: defaultRefresh,
|
||||
},
|
||||
refreshLock: &sync.RWMutex{},
|
||||
sender: &http.Client{},
|
||||
refreshCallbacks: callbacks,
|
||||
}
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromManualToken creates a ServicePrincipalToken using the supplied token
|
||||
func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID string, resource string, token Token, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token.IsZero() {
|
||||
return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized")
|
||||
}
|
||||
spt, err := NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalNoSecret{},
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt.inner.Token = token
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromManualTokenSecret creates a ServicePrincipalToken using the supplied token and secret
|
||||
func NewServicePrincipalTokenFromManualTokenSecret(oauthConfig OAuthConfig, clientID string, resource string, token Token, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("parameter 'secret' cannot be nil")
|
||||
}
|
||||
if token.IsZero() {
|
||||
return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized")
|
||||
}
|
||||
spt, err := NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
secret,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt.inner.Token = token
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalToken creates a ServicePrincipalToken from the supplied Service Principal
|
||||
// credentials scoped to the named resource.
|
||||
func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(secret, "secret"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalTokenSecret{
|
||||
ClientSecret: secret,
|
||||
},
|
||||
callbacks...,
|
||||
)
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromCertificate creates a ServicePrincipalToken from the supplied pkcs12 bytes.
|
||||
func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID string, certificate *x509.Certificate, privateKey *rsa.PrivateKey, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if certificate == nil {
|
||||
return nil, fmt.Errorf("parameter 'certificate' cannot be nil")
|
||||
}
|
||||
if privateKey == nil {
|
||||
return nil, fmt.Errorf("parameter 'privateKey' cannot be nil")
|
||||
}
|
||||
return NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalCertificateSecret{
|
||||
PrivateKey: privateKey,
|
||||
Certificate: certificate,
|
||||
},
|
||||
callbacks...,
|
||||
)
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromUsernamePassword creates a ServicePrincipalToken from the username and password.
|
||||
func NewServicePrincipalTokenFromUsernamePassword(oauthConfig OAuthConfig, clientID string, username string, password string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(username, "username"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(password, "password"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalUsernamePasswordSecret{
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
callbacks...,
|
||||
)
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromAuthorizationCode creates a ServicePrincipalToken from the
|
||||
func NewServicePrincipalTokenFromAuthorizationCode(oauthConfig OAuthConfig, clientID string, clientSecret string, authorizationCode string, redirectURI string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientSecret, "clientSecret"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(authorizationCode, "authorizationCode"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(redirectURI, "redirectURI"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalAuthorizationCodeSecret{
|
||||
ClientSecret: clientSecret,
|
||||
AuthorizationCode: authorizationCode,
|
||||
RedirectURI: redirectURI,
|
||||
},
|
||||
callbacks...,
|
||||
)
|
||||
}
|
||||
|
||||
// GetMSIVMEndpoint gets the MSI endpoint on Virtual Machines.
|
||||
func GetMSIVMEndpoint() (string, error) {
|
||||
return msiEndpoint, nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension.
|
||||
// It will use the system assigned identity when creating the token.
|
||||
func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, callbacks...)
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromMSIWithUserAssignedID creates a ServicePrincipalToken via the MSI VM Extension.
|
||||
// It will use the specified user assigned identity when creating the token.
|
||||
func NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource string, userAssignedID string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
return newServicePrincipalTokenFromMSI(msiEndpoint, resource, &userAssignedID, callbacks...)
|
||||
}
|
||||
|
||||
func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedID *string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateStringParam(msiEndpoint, "msiEndpoint"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userAssignedID != nil {
|
||||
if err := validateStringParam(*userAssignedID, "userAssignedID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// We set the oauth config token endpoint to be MSI's endpoint
|
||||
msiEndpointURL, err := url.Parse(msiEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("resource", resource)
|
||||
v.Set("api-version", "2018-02-01")
|
||||
if userAssignedID != nil {
|
||||
v.Set("client_id", *userAssignedID)
|
||||
}
|
||||
msiEndpointURL.RawQuery = v.Encode()
|
||||
|
||||
spt := &ServicePrincipalToken{
|
||||
inner: servicePrincipalToken{
|
||||
OauthConfig: OAuthConfig{
|
||||
TokenEndpoint: *msiEndpointURL,
|
||||
},
|
||||
Secret: &ServicePrincipalMSISecret{},
|
||||
Resource: resource,
|
||||
AutoRefresh: true,
|
||||
RefreshWithin: defaultRefresh,
|
||||
},
|
||||
refreshLock: &sync.RWMutex{},
|
||||
sender: &http.Client{},
|
||||
refreshCallbacks: callbacks,
|
||||
MaxMSIRefreshAttempts: defaultMaxMSIRefreshAttempts,
|
||||
}
|
||||
|
||||
if userAssignedID != nil {
|
||||
spt.inner.ClientID = *userAssignedID
|
||||
}
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
// internal type that implements TokenRefreshError
|
||||
type tokenRefreshError struct {
|
||||
message string
|
||||
resp *http.Response
|
||||
}
|
||||
|
||||
// Error implements the error interface which is part of the TokenRefreshError interface.
|
||||
func (tre tokenRefreshError) Error() string {
|
||||
return tre.message
|
||||
}
|
||||
|
||||
// Response implements the TokenRefreshError interface, it returns the raw HTTP response from the refresh operation.
|
||||
func (tre tokenRefreshError) Response() *http.Response {
|
||||
return tre.resp
|
||||
}
|
||||
|
||||
func newTokenRefreshError(message string, resp *http.Response) TokenRefreshError {
|
||||
return tokenRefreshError{message: message, resp: resp}
|
||||
}
|
||||
|
||||
// EnsureFresh will refresh the token if it will expire within the refresh window (as set by
|
||||
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
|
||||
func (spt *ServicePrincipalToken) EnsureFresh() error {
|
||||
return spt.EnsureFreshWithContext(context.Background())
|
||||
}
|
||||
|
||||
// EnsureFreshWithContext will refresh the token if it will expire within the refresh window (as set by
|
||||
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
|
||||
func (spt *ServicePrincipalToken) EnsureFreshWithContext(ctx context.Context) error {
|
||||
if spt.inner.AutoRefresh && spt.inner.Token.WillExpireIn(spt.inner.RefreshWithin) {
|
||||
// take the write lock then check to see if the token was already refreshed
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
if spt.inner.Token.WillExpireIn(spt.inner.RefreshWithin) {
|
||||
return spt.refreshInternal(ctx, spt.inner.Resource)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InvokeRefreshCallbacks calls any TokenRefreshCallbacks that were added to the SPT during initialization
|
||||
func (spt *ServicePrincipalToken) InvokeRefreshCallbacks(token Token) error {
|
||||
if spt.refreshCallbacks != nil {
|
||||
for _, callback := range spt.refreshCallbacks {
|
||||
err := callback(spt.inner.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: TokenRefreshCallback handler failed. Error = '%v'", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresh obtains a fresh token for the Service Principal.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) Refresh() error {
|
||||
return spt.RefreshWithContext(context.Background())
|
||||
}
|
||||
|
||||
// RefreshWithContext obtains a fresh token for the Service Principal.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshWithContext(ctx context.Context) error {
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
return spt.refreshInternal(ctx, spt.inner.Resource)
|
||||
}
|
||||
|
||||
// RefreshExchange refreshes the token, but for a different resource.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshExchange(resource string) error {
|
||||
return spt.RefreshExchangeWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// RefreshExchangeWithContext refreshes the token, but for a different resource.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshExchangeWithContext(ctx context.Context, resource string) error {
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
return spt.refreshInternal(ctx, resource)
|
||||
}
|
||||
|
||||
func (spt *ServicePrincipalToken) getGrantType() string {
|
||||
switch spt.inner.Secret.(type) {
|
||||
case *ServicePrincipalUsernamePasswordSecret:
|
||||
return OAuthGrantTypeUserPass
|
||||
case *ServicePrincipalAuthorizationCodeSecret:
|
||||
return OAuthGrantTypeAuthorizationCode
|
||||
default:
|
||||
return OAuthGrantTypeClientCredentials
|
||||
}
|
||||
}
|
||||
|
||||
func isIMDS(u url.URL) bool {
|
||||
imds, err := url.Parse(msiEndpoint)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return u.Host == imds.Host && u.Path == imds.Path
|
||||
}
|
||||
|
||||
func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource string) error {
|
||||
req, err := http.NewRequest(http.MethodPost, spt.inner.OauthConfig.TokenEndpoint.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: Failed to build the refresh request. Error = '%v'", err)
|
||||
}
|
||||
req.Header.Add("User-Agent", version.UserAgent())
|
||||
req = req.WithContext(ctx)
|
||||
if !isIMDS(spt.inner.OauthConfig.TokenEndpoint) {
|
||||
v := url.Values{}
|
||||
v.Set("client_id", spt.inner.ClientID)
|
||||
v.Set("resource", resource)
|
||||
|
||||
if spt.inner.Token.RefreshToken != "" {
|
||||
v.Set("grant_type", OAuthGrantTypeRefreshToken)
|
||||
v.Set("refresh_token", spt.inner.Token.RefreshToken)
|
||||
// web apps must specify client_secret when refreshing tokens
|
||||
// see https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code#refreshing-the-access-tokens
|
||||
if spt.getGrantType() == OAuthGrantTypeAuthorizationCode {
|
||||
err := spt.inner.Secret.SetAuthenticationValues(spt, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
v.Set("grant_type", spt.getGrantType())
|
||||
err := spt.inner.Secret.SetAuthenticationValues(spt, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s := v.Encode()
|
||||
body := ioutil.NopCloser(strings.NewReader(s))
|
||||
req.ContentLength = int64(len(s))
|
||||
req.Header.Set(contentType, mimeTypeFormPost)
|
||||
req.Body = body
|
||||
}
|
||||
|
||||
if _, ok := spt.inner.Secret.(*ServicePrincipalMSISecret); ok {
|
||||
req.Method = http.MethodGet
|
||||
req.Header.Set(metadataHeader, "true")
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
if isIMDS(spt.inner.OauthConfig.TokenEndpoint) {
|
||||
resp, err = retryForIMDS(spt.sender, req, spt.MaxMSIRefreshAttempts)
|
||||
} else {
|
||||
resp, err = spt.sender.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Failed to execute the refresh request. Error = '%v'", err), nil)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
rb, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if err != nil {
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body: %v", resp.StatusCode, err), resp)
|
||||
}
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb)), resp)
|
||||
}
|
||||
|
||||
// for the following error cases don't return a TokenRefreshError. the operation succeeded
|
||||
// but some transient failure happened during deserialization. by returning a generic error
|
||||
// the retry logic will kick in (we don't retry on TokenRefreshError).
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: Failed to read a new service principal token during refresh. Error = '%v'", err)
|
||||
}
|
||||
if len(strings.Trim(string(rb), " ")) == 0 {
|
||||
return fmt.Errorf("adal: Empty service principal token received during refresh")
|
||||
}
|
||||
var token Token
|
||||
err = json.Unmarshal(rb, &token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: Failed to unmarshal the service principal token during refresh. Error = '%v' JSON = '%s'", err, string(rb))
|
||||
}
|
||||
|
||||
spt.inner.Token = token
|
||||
|
||||
return spt.InvokeRefreshCallbacks(token)
|
||||
}
|
||||
|
||||
// retry logic specific to retrieving a token from the IMDS endpoint
|
||||
func retryForIMDS(sender Sender, req *http.Request, maxAttempts int) (resp *http.Response, err error) {
|
||||
// copied from client.go due to circular dependency
|
||||
retries := []int{
|
||||
http.StatusRequestTimeout, // 408
|
||||
http.StatusTooManyRequests, // 429
|
||||
http.StatusInternalServerError, // 500
|
||||
http.StatusBadGateway, // 502
|
||||
http.StatusServiceUnavailable, // 503
|
||||
http.StatusGatewayTimeout, // 504
|
||||
}
|
||||
// extra retry status codes specific to IMDS
|
||||
retries = append(retries,
|
||||
http.StatusNotFound,
|
||||
http.StatusGone,
|
||||
// all remaining 5xx
|
||||
http.StatusNotImplemented,
|
||||
http.StatusHTTPVersionNotSupported,
|
||||
http.StatusVariantAlsoNegotiates,
|
||||
http.StatusInsufficientStorage,
|
||||
http.StatusLoopDetected,
|
||||
http.StatusNotExtended,
|
||||
http.StatusNetworkAuthenticationRequired)
|
||||
|
||||
// see https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/how-to-use-vm-token#retry-guidance
|
||||
|
||||
const maxDelay time.Duration = 60 * time.Second
|
||||
|
||||
attempt := 0
|
||||
delay := time.Duration(0)
|
||||
|
||||
for attempt < maxAttempts {
|
||||
resp, err = sender.Do(req)
|
||||
// retry on temporary network errors, e.g. transient network failures.
|
||||
// if we don't receive a response then assume we can't connect to the
|
||||
// endpoint so we're likely not running on an Azure VM so don't retry.
|
||||
if (err != nil && !isTemporaryNetworkError(err)) || resp == nil || resp.StatusCode == http.StatusOK || !containsInt(retries, resp.StatusCode) {
|
||||
return
|
||||
}
|
||||
|
||||
// perform exponential backoff with a cap.
|
||||
// must increment attempt before calculating delay.
|
||||
attempt++
|
||||
// the base value of 2 is the "delta backoff" as specified in the guidance doc
|
||||
delay += (time.Duration(math.Pow(2, float64(attempt))) * time.Second)
|
||||
if delay > maxDelay {
|
||||
delay = maxDelay
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(delay):
|
||||
// intentionally left blank
|
||||
case <-req.Context().Done():
|
||||
err = req.Context().Err()
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// returns true if the specified error is a temporary network error or false if it's not.
|
||||
// if the error doesn't implement the net.Error interface the return value is true.
|
||||
func isTemporaryNetworkError(err error) bool {
|
||||
if netErr, ok := err.(net.Error); !ok || (ok && netErr.Temporary()) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// returns true if slice ints contains the value n
|
||||
func containsInt(ints []int, n int) bool {
|
||||
for _, i := range ints {
|
||||
if i == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAutoRefresh enables or disables automatic refreshing of stale tokens.
|
||||
func (spt *ServicePrincipalToken) SetAutoRefresh(autoRefresh bool) {
|
||||
spt.inner.AutoRefresh = autoRefresh
|
||||
}
|
||||
|
||||
// SetRefreshWithin sets the interval within which if the token will expire, EnsureFresh will
|
||||
// refresh the token.
|
||||
func (spt *ServicePrincipalToken) SetRefreshWithin(d time.Duration) {
|
||||
spt.inner.RefreshWithin = d
|
||||
return
|
||||
}
|
||||
|
||||
// SetSender sets the http.Client used when obtaining the Service Principal token. An
|
||||
// undecorated http.Client is used by default.
|
||||
func (spt *ServicePrincipalToken) SetSender(s Sender) { spt.sender = s }
|
||||
|
||||
// OAuthToken implements the OAuthTokenProvider interface. It returns the current access token.
|
||||
func (spt *ServicePrincipalToken) OAuthToken() string {
|
||||
spt.refreshLock.RLock()
|
||||
defer spt.refreshLock.RUnlock()
|
||||
return spt.inner.Token.OAuthToken()
|
||||
}
|
||||
|
||||
// Token returns a copy of the current token.
|
||||
func (spt *ServicePrincipalToken) Token() Token {
|
||||
spt.refreshLock.RLock()
|
||||
defer spt.refreshLock.RUnlock()
|
||||
return spt.inner.Token
|
||||
}
|
||||
259
vendor/github.com/Azure/go-autorest/autorest/authorization.go
generated
vendored
259
vendor/github.com/Azure/go-autorest/autorest/authorization.go
generated
vendored
@@ -1,259 +0,0 @@
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
)
|
||||
|
||||
const (
|
||||
bearerChallengeHeader = "Www-Authenticate"
|
||||
bearer = "Bearer"
|
||||
tenantID = "tenantID"
|
||||
apiKeyAuthorizerHeader = "Ocp-Apim-Subscription-Key"
|
||||
bingAPISdkHeader = "X-BingApis-SDK-Client"
|
||||
golangBingAPISdkHeaderValue = "Go-SDK"
|
||||
)
|
||||
|
||||
// Authorizer is the interface that provides a PrepareDecorator used to supply request
|
||||
// authorization. Most often, the Authorizer decorator runs last so it has access to the full
|
||||
// state of the formed HTTP request.
|
||||
type Authorizer interface {
|
||||
WithAuthorization() PrepareDecorator
|
||||
}
|
||||
|
||||
// NullAuthorizer implements a default, "do nothing" Authorizer.
|
||||
type NullAuthorizer struct{}
|
||||
|
||||
// WithAuthorization returns a PrepareDecorator that does nothing.
|
||||
func (na NullAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
return WithNothing()
|
||||
}
|
||||
|
||||
// APIKeyAuthorizer implements API Key authorization.
|
||||
type APIKeyAuthorizer struct {
|
||||
headers map[string]interface{}
|
||||
queryParameters map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAPIKeyAuthorizerWithHeaders creates an ApiKeyAuthorizer with headers.
|
||||
func NewAPIKeyAuthorizerWithHeaders(headers map[string]interface{}) *APIKeyAuthorizer {
|
||||
return NewAPIKeyAuthorizer(headers, nil)
|
||||
}
|
||||
|
||||
// NewAPIKeyAuthorizerWithQueryParameters creates an ApiKeyAuthorizer with query parameters.
|
||||
func NewAPIKeyAuthorizerWithQueryParameters(queryParameters map[string]interface{}) *APIKeyAuthorizer {
|
||||
return NewAPIKeyAuthorizer(nil, queryParameters)
|
||||
}
|
||||
|
||||
// NewAPIKeyAuthorizer creates an ApiKeyAuthorizer with headers.
|
||||
func NewAPIKeyAuthorizer(headers map[string]interface{}, queryParameters map[string]interface{}) *APIKeyAuthorizer {
|
||||
return &APIKeyAuthorizer{headers: headers, queryParameters: queryParameters}
|
||||
}
|
||||
|
||||
// WithAuthorization returns a PrepareDecorator that adds an HTTP headers and Query Paramaters
|
||||
func (aka *APIKeyAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return DecoratePreparer(p, WithHeaders(aka.headers), WithQueryParameters(aka.queryParameters))
|
||||
}
|
||||
}
|
||||
|
||||
// CognitiveServicesAuthorizer implements authorization for Cognitive Services.
|
||||
type CognitiveServicesAuthorizer struct {
|
||||
subscriptionKey string
|
||||
}
|
||||
|
||||
// NewCognitiveServicesAuthorizer is
|
||||
func NewCognitiveServicesAuthorizer(subscriptionKey string) *CognitiveServicesAuthorizer {
|
||||
return &CognitiveServicesAuthorizer{subscriptionKey: subscriptionKey}
|
||||
}
|
||||
|
||||
// WithAuthorization is
|
||||
func (csa *CognitiveServicesAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
headers := make(map[string]interface{})
|
||||
headers[apiKeyAuthorizerHeader] = csa.subscriptionKey
|
||||
headers[bingAPISdkHeader] = golangBingAPISdkHeaderValue
|
||||
|
||||
return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization()
|
||||
}
|
||||
|
||||
// BearerAuthorizer implements the bearer authorization
|
||||
type BearerAuthorizer struct {
|
||||
tokenProvider adal.OAuthTokenProvider
|
||||
}
|
||||
|
||||
// NewBearerAuthorizer crates a BearerAuthorizer using the given token provider
|
||||
func NewBearerAuthorizer(tp adal.OAuthTokenProvider) *BearerAuthorizer {
|
||||
return &BearerAuthorizer{tokenProvider: tp}
|
||||
}
|
||||
|
||||
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
|
||||
// value is "Bearer " followed by the token.
|
||||
//
|
||||
// By default, the token will be automatically refreshed through the Refresher interface.
|
||||
func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
// the ordering is important here, prefer RefresherWithContext if available
|
||||
if refresher, ok := ba.tokenProvider.(adal.RefresherWithContext); ok {
|
||||
err = refresher.EnsureFreshWithContext(r.Context())
|
||||
} else if refresher, ok := ba.tokenProvider.(adal.Refresher); ok {
|
||||
err = refresher.EnsureFresh()
|
||||
}
|
||||
if err != nil {
|
||||
var resp *http.Response
|
||||
if tokError, ok := err.(adal.TokenRefreshError); ok {
|
||||
resp = tokError.Response()
|
||||
}
|
||||
return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", resp,
|
||||
"Failed to refresh the Token for request to %s", r.URL)
|
||||
}
|
||||
return Prepare(r, WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", ba.tokenProvider.OAuthToken())))
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BearerAuthorizerCallbackFunc is the authentication callback signature.
|
||||
type BearerAuthorizerCallbackFunc func(tenantID, resource string) (*BearerAuthorizer, error)
|
||||
|
||||
// BearerAuthorizerCallback implements bearer authorization via a callback.
|
||||
type BearerAuthorizerCallback struct {
|
||||
sender Sender
|
||||
callback BearerAuthorizerCallbackFunc
|
||||
}
|
||||
|
||||
// NewBearerAuthorizerCallback creates a bearer authorization callback. The callback
|
||||
// is invoked when the HTTP request is submitted.
|
||||
func NewBearerAuthorizerCallback(sender Sender, callback BearerAuthorizerCallbackFunc) *BearerAuthorizerCallback {
|
||||
if sender == nil {
|
||||
sender = &http.Client{}
|
||||
}
|
||||
return &BearerAuthorizerCallback{sender: sender, callback: callback}
|
||||
}
|
||||
|
||||
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose value
|
||||
// is "Bearer " followed by the token. The BearerAuthorizer is obtained via a user-supplied callback.
|
||||
//
|
||||
// By default, the token will be automatically refreshed through the Refresher interface.
|
||||
func (bacb *BearerAuthorizerCallback) WithAuthorization() PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
// make a copy of the request and remove the body as it's not
|
||||
// required and avoids us having to create a copy of it.
|
||||
rCopy := *r
|
||||
removeRequestBody(&rCopy)
|
||||
|
||||
resp, err := bacb.sender.Do(&rCopy)
|
||||
if err == nil && resp.StatusCode == 401 {
|
||||
defer resp.Body.Close()
|
||||
if hasBearerChallenge(resp) {
|
||||
bc, err := newBearerChallenge(resp)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if bacb.callback != nil {
|
||||
ba, err := bacb.callback(bc.values[tenantID], bc.values["resource"])
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return Prepare(r, ba.WithAuthorization())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the HTTP response contains a bearer challenge
|
||||
func hasBearerChallenge(resp *http.Response) bool {
|
||||
authHeader := resp.Header.Get(bearerChallengeHeader)
|
||||
if len(authHeader) == 0 || strings.Index(authHeader, bearer) < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type bearerChallenge struct {
|
||||
values map[string]string
|
||||
}
|
||||
|
||||
func newBearerChallenge(resp *http.Response) (bc bearerChallenge, err error) {
|
||||
challenge := strings.TrimSpace(resp.Header.Get(bearerChallengeHeader))
|
||||
trimmedChallenge := challenge[len(bearer)+1:]
|
||||
|
||||
// challenge is a set of key=value pairs that are comma delimited
|
||||
pairs := strings.Split(trimmedChallenge, ",")
|
||||
if len(pairs) < 1 {
|
||||
err = fmt.Errorf("challenge '%s' contains no pairs", challenge)
|
||||
return bc, err
|
||||
}
|
||||
|
||||
bc.values = make(map[string]string)
|
||||
for i := range pairs {
|
||||
trimmedPair := strings.TrimSpace(pairs[i])
|
||||
pair := strings.Split(trimmedPair, "=")
|
||||
if len(pair) == 2 {
|
||||
// remove the enclosing quotes
|
||||
key := strings.Trim(pair[0], "\"")
|
||||
value := strings.Trim(pair[1], "\"")
|
||||
|
||||
switch key {
|
||||
case "authorization", "authorization_uri":
|
||||
// strip the tenant ID from the authorization URL
|
||||
asURL, err := url.Parse(value)
|
||||
if err != nil {
|
||||
return bc, err
|
||||
}
|
||||
bc.values[tenantID] = asURL.Path[1:]
|
||||
default:
|
||||
bc.values[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bc, err
|
||||
}
|
||||
|
||||
// EventGridKeyAuthorizer implements authorization for event grid using key authentication.
|
||||
type EventGridKeyAuthorizer struct {
|
||||
topicKey string
|
||||
}
|
||||
|
||||
// NewEventGridKeyAuthorizer creates a new EventGridKeyAuthorizer
|
||||
// with the specified topic key.
|
||||
func NewEventGridKeyAuthorizer(topicKey string) EventGridKeyAuthorizer {
|
||||
return EventGridKeyAuthorizer{topicKey: topicKey}
|
||||
}
|
||||
|
||||
// WithAuthorization returns a PrepareDecorator that adds the aeg-sas-key authentication header.
|
||||
func (egta EventGridKeyAuthorizer) WithAuthorization() PrepareDecorator {
|
||||
headers := map[string]interface{}{
|
||||
"aeg-sas-key": egta.topicKey,
|
||||
}
|
||||
return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization()
|
||||
}
|
||||
150
vendor/github.com/Azure/go-autorest/autorest/autorest.go
generated
vendored
150
vendor/github.com/Azure/go-autorest/autorest/autorest.go
generated
vendored
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
Package autorest implements an HTTP request pipeline suitable for use across multiple go-routines
|
||||
and provides the shared routines relied on by AutoRest (see https://github.com/Azure/autorest/)
|
||||
generated Go code.
|
||||
|
||||
The package breaks sending and responding to HTTP requests into three phases: Preparing, Sending,
|
||||
and Responding. A typical pattern is:
|
||||
|
||||
req, err := Prepare(&http.Request{},
|
||||
token.WithAuthorization())
|
||||
|
||||
resp, err := Send(req,
|
||||
WithLogging(logger),
|
||||
DoErrorIfStatusCode(http.StatusInternalServerError),
|
||||
DoCloseIfError(),
|
||||
DoRetryForAttempts(5, time.Second))
|
||||
|
||||
err = Respond(resp,
|
||||
ByDiscardingBody(),
|
||||
ByClosing())
|
||||
|
||||
Each phase relies on decorators to modify and / or manage processing. Decorators may first modify
|
||||
and then pass the data along, pass the data first and then modify the result, or wrap themselves
|
||||
around passing the data (such as a logger might do). Decorators run in the order provided. For
|
||||
example, the following:
|
||||
|
||||
req, err := Prepare(&http.Request{},
|
||||
WithBaseURL("https://microsoft.com/"),
|
||||
WithPath("a"),
|
||||
WithPath("b"),
|
||||
WithPath("c"))
|
||||
|
||||
will set the URL to:
|
||||
|
||||
https://microsoft.com/a/b/c
|
||||
|
||||
Preparers and Responders may be shared and re-used (assuming the underlying decorators support
|
||||
sharing and re-use). Performant use is obtained by creating one or more Preparers and Responders
|
||||
shared among multiple go-routines, and a single Sender shared among multiple sending go-routines,
|
||||
all bound together by means of input / output channels.
|
||||
|
||||
Decorators hold their passed state within a closure (such as the path components in the example
|
||||
above). Be careful to share Preparers and Responders only in a context where such held state
|
||||
applies. For example, it may not make sense to share a Preparer that applies a query string from a
|
||||
fixed set of values. Similarly, sharing a Responder that reads the response body into a passed
|
||||
struct (e.g., ByUnmarshallingJson) is likely incorrect.
|
||||
|
||||
Lastly, the Swagger specification (https://swagger.io) that drives AutoRest
|
||||
(https://github.com/Azure/autorest/) precisely defines two date forms: date and date-time. The
|
||||
github.com/Azure/go-autorest/autorest/date package provides time.Time derivations to ensure
|
||||
correct parsing and formatting.
|
||||
|
||||
Errors raised by autorest objects and methods will conform to the autorest.Error interface.
|
||||
|
||||
See the included examples for more detail. For details on the suggested use of this package by
|
||||
generated clients, see the Client described below.
|
||||
*/
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// HeaderLocation specifies the HTTP Location header.
|
||||
HeaderLocation = "Location"
|
||||
|
||||
// HeaderRetryAfter specifies the HTTP Retry-After header.
|
||||
HeaderRetryAfter = "Retry-After"
|
||||
)
|
||||
|
||||
// ResponseHasStatusCode returns true if the status code in the HTTP Response is in the passed set
|
||||
// and false otherwise.
|
||||
func ResponseHasStatusCode(resp *http.Response, codes ...int) bool {
|
||||
if resp == nil {
|
||||
return false
|
||||
}
|
||||
return containsInt(codes, resp.StatusCode)
|
||||
}
|
||||
|
||||
// GetLocation retrieves the URL from the Location header of the passed response.
|
||||
func GetLocation(resp *http.Response) string {
|
||||
return resp.Header.Get(HeaderLocation)
|
||||
}
|
||||
|
||||
// GetRetryAfter extracts the retry delay from the Retry-After header of the passed response. If
|
||||
// the header is absent or is malformed, it will return the supplied default delay time.Duration.
|
||||
func GetRetryAfter(resp *http.Response, defaultDelay time.Duration) time.Duration {
|
||||
retry := resp.Header.Get(HeaderRetryAfter)
|
||||
if retry == "" {
|
||||
return defaultDelay
|
||||
}
|
||||
|
||||
d, err := time.ParseDuration(retry + "s")
|
||||
if err != nil {
|
||||
return defaultDelay
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// NewPollingRequest allocates and returns a new http.Request to poll for the passed response.
|
||||
func NewPollingRequest(resp *http.Response, cancel <-chan struct{}) (*http.Request, error) {
|
||||
location := GetLocation(resp)
|
||||
if location == "" {
|
||||
return nil, NewErrorWithResponse("autorest", "NewPollingRequest", resp, "Location header missing from response that requires polling")
|
||||
}
|
||||
|
||||
req, err := Prepare(&http.Request{Cancel: cancel},
|
||||
AsGet(),
|
||||
WithBaseURL(location))
|
||||
if err != nil {
|
||||
return nil, NewErrorWithError(err, "autorest", "NewPollingRequest", nil, "Failure creating poll request to %s", location)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewPollingRequestWithContext allocates and returns a new http.Request with the specified context to poll for the passed response.
|
||||
func NewPollingRequestWithContext(ctx context.Context, resp *http.Response) (*http.Request, error) {
|
||||
location := GetLocation(resp)
|
||||
if location == "" {
|
||||
return nil, NewErrorWithResponse("autorest", "NewPollingRequestWithContext", resp, "Location header missing from response that requires polling")
|
||||
}
|
||||
|
||||
req, err := Prepare((&http.Request{}).WithContext(ctx),
|
||||
AsGet(),
|
||||
WithBaseURL(location))
|
||||
if err != nil {
|
||||
return nil, NewErrorWithError(err, "autorest", "NewPollingRequestWithContext", nil, "Failure creating poll request to %s", location)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
916
vendor/github.com/Azure/go-autorest/autorest/azure/async.go
generated
vendored
916
vendor/github.com/Azure/go-autorest/autorest/azure/async.go
generated
vendored
@@ -1,916 +0,0 @@
|
||||
package azure
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
)
|
||||
|
||||
const (
|
||||
headerAsyncOperation = "Azure-AsyncOperation"
|
||||
)
|
||||
|
||||
const (
|
||||
operationInProgress string = "InProgress"
|
||||
operationCanceled string = "Canceled"
|
||||
operationFailed string = "Failed"
|
||||
operationSucceeded string = "Succeeded"
|
||||
)
|
||||
|
||||
var pollingCodes = [...]int{http.StatusNoContent, http.StatusAccepted, http.StatusCreated, http.StatusOK}
|
||||
|
||||
// Future provides a mechanism to access the status and results of an asynchronous request.
|
||||
// Since futures are stateful they should be passed by value to avoid race conditions.
|
||||
type Future struct {
|
||||
req *http.Request // legacy
|
||||
pt pollingTracker
|
||||
}
|
||||
|
||||
// NewFuture returns a new Future object initialized with the specified request.
|
||||
// Deprecated: Please use NewFutureFromResponse instead.
|
||||
func NewFuture(req *http.Request) Future {
|
||||
return Future{req: req}
|
||||
}
|
||||
|
||||
// NewFutureFromResponse returns a new Future object initialized
|
||||
// with the initial response from an asynchronous operation.
|
||||
func NewFutureFromResponse(resp *http.Response) (Future, error) {
|
||||
pt, err := createPollingTracker(resp)
|
||||
if err != nil {
|
||||
return Future{}, err
|
||||
}
|
||||
return Future{pt: pt}, nil
|
||||
}
|
||||
|
||||
// Response returns the last HTTP response.
|
||||
func (f Future) Response() *http.Response {
|
||||
if f.pt == nil {
|
||||
return nil
|
||||
}
|
||||
return f.pt.latestResponse()
|
||||
}
|
||||
|
||||
// Status returns the last status message of the operation.
|
||||
func (f Future) Status() string {
|
||||
if f.pt == nil {
|
||||
return ""
|
||||
}
|
||||
return f.pt.pollingStatus()
|
||||
}
|
||||
|
||||
// PollingMethod returns the method used to monitor the status of the asynchronous operation.
|
||||
func (f Future) PollingMethod() PollingMethodType {
|
||||
if f.pt == nil {
|
||||
return PollingUnknown
|
||||
}
|
||||
return f.pt.pollingMethod()
|
||||
}
|
||||
|
||||
// Done queries the service to see if the operation has completed.
|
||||
func (f *Future) Done(sender autorest.Sender) (bool, error) {
|
||||
// support for legacy Future implementation
|
||||
if f.req != nil {
|
||||
resp, err := sender.Do(f.req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
pt, err := createPollingTracker(resp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
f.pt = pt
|
||||
f.req = nil
|
||||
}
|
||||
// end legacy
|
||||
if f.pt == nil {
|
||||
return false, autorest.NewError("Future", "Done", "future is not initialized")
|
||||
}
|
||||
if f.pt.hasTerminated() {
|
||||
return true, f.pt.pollingError()
|
||||
}
|
||||
if err := f.pt.pollForStatus(sender); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := f.pt.checkForErrors(); err != nil {
|
||||
return f.pt.hasTerminated(), err
|
||||
}
|
||||
if err := f.pt.updatePollingState(f.pt.provisioningStateApplicable()); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := f.pt.updateHeaders(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return f.pt.hasTerminated(), f.pt.pollingError()
|
||||
}
|
||||
|
||||
// GetPollingDelay returns a duration the application should wait before checking
|
||||
// the status of the asynchronous request and true; this value is returned from
|
||||
// the service via the Retry-After response header. If the header wasn't returned
|
||||
// then the function returns the zero-value time.Duration and false.
|
||||
func (f Future) GetPollingDelay() (time.Duration, bool) {
|
||||
if f.pt == nil {
|
||||
return 0, false
|
||||
}
|
||||
resp := f.pt.latestResponse()
|
||||
if resp == nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
retry := resp.Header.Get(autorest.HeaderRetryAfter)
|
||||
if retry == "" {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
d, err := time.ParseDuration(retry + "s")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return d, true
|
||||
}
|
||||
|
||||
// WaitForCompletion will return when one of the following conditions is met: the long
|
||||
// running operation has completed, the provided context is cancelled, or the client's
|
||||
// polling duration has been exceeded. It will retry failed polling attempts based on
|
||||
// the retry value defined in the client up to the maximum retry attempts.
|
||||
// Deprecated: Please use WaitForCompletionRef() instead.
|
||||
func (f Future) WaitForCompletion(ctx context.Context, client autorest.Client) error {
|
||||
return f.WaitForCompletionRef(ctx, client)
|
||||
}
|
||||
|
||||
// WaitForCompletionRef will return when one of the following conditions is met: the long
|
||||
// running operation has completed, the provided context is cancelled, or the client's
|
||||
// polling duration has been exceeded. It will retry failed polling attempts based on
|
||||
// the retry value defined in the client up to the maximum retry attempts.
|
||||
func (f *Future) WaitForCompletionRef(ctx context.Context, client autorest.Client) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, client.PollingDuration)
|
||||
defer cancel()
|
||||
done, err := f.Done(client)
|
||||
for attempts := 0; !done; done, err = f.Done(client) {
|
||||
if attempts >= client.RetryAttempts {
|
||||
return autorest.NewErrorWithError(err, "Future", "WaitForCompletion", f.pt.latestResponse(), "the number of retries has been exceeded")
|
||||
}
|
||||
// we want delayAttempt to be zero in the non-error case so
|
||||
// that DelayForBackoff doesn't perform exponential back-off
|
||||
var delayAttempt int
|
||||
var delay time.Duration
|
||||
if err == nil {
|
||||
// check for Retry-After delay, if not present use the client's polling delay
|
||||
var ok bool
|
||||
delay, ok = f.GetPollingDelay()
|
||||
if !ok {
|
||||
delay = client.PollingDelay
|
||||
}
|
||||
} else {
|
||||
// there was an error polling for status so perform exponential
|
||||
// back-off based on the number of attempts using the client's retry
|
||||
// duration. update attempts after delayAttempt to avoid off-by-one.
|
||||
delayAttempt = attempts
|
||||
delay = client.RetryDuration
|
||||
attempts++
|
||||
}
|
||||
// wait until the delay elapses or the context is cancelled
|
||||
delayElapsed := autorest.DelayForBackoff(delay, delayAttempt, ctx.Done())
|
||||
if !delayElapsed {
|
||||
return autorest.NewErrorWithError(ctx.Err(), "Future", "WaitForCompletion", f.pt.latestResponse(), "context has been cancelled")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (f Future) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(f.pt)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (f *Future) UnmarshalJSON(data []byte) error {
|
||||
// unmarshal into JSON object to determine the tracker type
|
||||
obj := map[string]interface{}{}
|
||||
err := json.Unmarshal(data, &obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if obj["method"] == nil {
|
||||
return autorest.NewError("Future", "UnmarshalJSON", "missing 'method' property")
|
||||
}
|
||||
method := obj["method"].(string)
|
||||
switch strings.ToUpper(method) {
|
||||
case http.MethodDelete:
|
||||
f.pt = &pollingTrackerDelete{}
|
||||
case http.MethodPatch:
|
||||
f.pt = &pollingTrackerPatch{}
|
||||
case http.MethodPost:
|
||||
f.pt = &pollingTrackerPost{}
|
||||
case http.MethodPut:
|
||||
f.pt = &pollingTrackerPut{}
|
||||
default:
|
||||
return autorest.NewError("Future", "UnmarshalJSON", "unsupoorted method '%s'", method)
|
||||
}
|
||||
// now unmarshal into the tracker
|
||||
return json.Unmarshal(data, &f.pt)
|
||||
}
|
||||
|
||||
// PollingURL returns the URL used for retrieving the status of the long-running operation.
|
||||
func (f Future) PollingURL() string {
|
||||
if f.pt == nil {
|
||||
return ""
|
||||
}
|
||||
return f.pt.pollingURL()
|
||||
}
|
||||
|
||||
// GetResult should be called once polling has completed successfully.
|
||||
// It makes the final GET call to retrieve the resultant payload.
|
||||
func (f Future) GetResult(sender autorest.Sender) (*http.Response, error) {
|
||||
if f.pt.finalGetURL() == "" {
|
||||
// we can end up in this situation if the async operation returns a 200
|
||||
// with no polling URLs. in that case return the response which should
|
||||
// contain the JSON payload (only do this for successful terminal cases).
|
||||
if lr := f.pt.latestResponse(); lr != nil && f.pt.hasSucceeded() {
|
||||
return lr, nil
|
||||
}
|
||||
return nil, autorest.NewError("Future", "GetResult", "missing URL for retrieving result")
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, f.pt.finalGetURL(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sender.Do(req)
|
||||
}
|
||||
|
||||
type pollingTracker interface {
|
||||
// these methods can differ per tracker
|
||||
|
||||
// checks the response headers and status code to determine the polling mechanism
|
||||
updateHeaders() error
|
||||
|
||||
// checks the response for tracker-specific error conditions
|
||||
checkForErrors() error
|
||||
|
||||
// returns true if provisioning state should be checked
|
||||
provisioningStateApplicable() bool
|
||||
|
||||
// methods common to all trackers
|
||||
|
||||
// initializes the tracker's internal state, call this when the tracker is created
|
||||
initializeState() error
|
||||
|
||||
// makes an HTTP request to check the status of the LRO
|
||||
pollForStatus(sender autorest.Sender) error
|
||||
|
||||
// updates internal tracker state, call this after each call to pollForStatus
|
||||
updatePollingState(provStateApl bool) error
|
||||
|
||||
// returns the error response from the service, can be nil
|
||||
pollingError() error
|
||||
|
||||
// returns the polling method being used
|
||||
pollingMethod() PollingMethodType
|
||||
|
||||
// returns the state of the LRO as returned from the service
|
||||
pollingStatus() string
|
||||
|
||||
// returns the URL used for polling status
|
||||
pollingURL() string
|
||||
|
||||
// returns the URL used for the final GET to retrieve the resource
|
||||
finalGetURL() string
|
||||
|
||||
// returns true if the LRO is in a terminal state
|
||||
hasTerminated() bool
|
||||
|
||||
// returns true if the LRO is in a failed terminal state
|
||||
hasFailed() bool
|
||||
|
||||
// returns true if the LRO is in a successful terminal state
|
||||
hasSucceeded() bool
|
||||
|
||||
// returns the cached HTTP response after a call to pollForStatus(), can be nil
|
||||
latestResponse() *http.Response
|
||||
}
|
||||
|
||||
type pollingTrackerBase struct {
|
||||
// resp is the last response, either from the submission of the LRO or from polling
|
||||
resp *http.Response
|
||||
|
||||
// method is the HTTP verb, this is needed for deserialization
|
||||
Method string `json:"method"`
|
||||
|
||||
// rawBody is the raw JSON response body
|
||||
rawBody map[string]interface{}
|
||||
|
||||
// denotes if polling is using async-operation or location header
|
||||
Pm PollingMethodType `json:"pollingMethod"`
|
||||
|
||||
// the URL to poll for status
|
||||
URI string `json:"pollingURI"`
|
||||
|
||||
// the state of the LRO as returned from the service
|
||||
State string `json:"lroState"`
|
||||
|
||||
// the URL to GET for the final result
|
||||
FinalGetURI string `json:"resultURI"`
|
||||
|
||||
// used to hold an error object returned from the service
|
||||
Err *ServiceError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerBase) initializeState() error {
|
||||
// determine the initial polling state based on response body and/or HTTP status
|
||||
// code. this is applicable to the initial LRO response, not polling responses!
|
||||
pt.Method = pt.resp.Request.Method
|
||||
if err := pt.updateRawBody(); err != nil {
|
||||
return err
|
||||
}
|
||||
switch pt.resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
if ps := pt.getProvisioningState(); ps != nil {
|
||||
pt.State = *ps
|
||||
} else {
|
||||
pt.State = operationSucceeded
|
||||
}
|
||||
case http.StatusCreated:
|
||||
if ps := pt.getProvisioningState(); ps != nil {
|
||||
pt.State = *ps
|
||||
} else {
|
||||
pt.State = operationInProgress
|
||||
}
|
||||
case http.StatusAccepted:
|
||||
pt.State = operationInProgress
|
||||
case http.StatusNoContent:
|
||||
pt.State = operationSucceeded
|
||||
default:
|
||||
pt.State = operationFailed
|
||||
pt.updateErrorFromResponse()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) getProvisioningState() *string {
|
||||
if pt.rawBody != nil && pt.rawBody["properties"] != nil {
|
||||
p := pt.rawBody["properties"].(map[string]interface{})
|
||||
if ps := p["provisioningState"]; ps != nil {
|
||||
s := ps.(string)
|
||||
return &s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerBase) updateRawBody() error {
|
||||
pt.rawBody = map[string]interface{}{}
|
||||
if pt.resp.ContentLength != 0 {
|
||||
defer pt.resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(pt.resp.Body)
|
||||
if err != nil {
|
||||
return autorest.NewErrorWithError(err, "pollingTrackerBase", "updateRawBody", nil, "failed to read response body")
|
||||
}
|
||||
// put the body back so it's available to other callers
|
||||
pt.resp.Body = ioutil.NopCloser(bytes.NewReader(b))
|
||||
if err = json.Unmarshal(b, &pt.rawBody); err != nil {
|
||||
return autorest.NewErrorWithError(err, "pollingTrackerBase", "updateRawBody", nil, "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerBase) pollForStatus(sender autorest.Sender) error {
|
||||
req, err := http.NewRequest(http.MethodGet, pt.URI, nil)
|
||||
if err != nil {
|
||||
return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed to create HTTP request")
|
||||
}
|
||||
// attach the context from the original request if available (it will be absent for deserialized futures)
|
||||
if pt.resp != nil {
|
||||
req = req.WithContext(pt.resp.Request.Context())
|
||||
}
|
||||
pt.resp, err = sender.Do(req)
|
||||
if err != nil {
|
||||
return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed to send HTTP request")
|
||||
}
|
||||
if autorest.ResponseHasStatusCode(pt.resp, pollingCodes[:]...) {
|
||||
// reset the service error on success case
|
||||
pt.Err = nil
|
||||
err = pt.updateRawBody()
|
||||
} else {
|
||||
// check response body for error content
|
||||
pt.updateErrorFromResponse()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// attempts to unmarshal a ServiceError type from the response body.
|
||||
// if that fails then make a best attempt at creating something meaningful.
|
||||
func (pt *pollingTrackerBase) updateErrorFromResponse() {
|
||||
var err error
|
||||
if pt.resp.ContentLength != 0 {
|
||||
type respErr struct {
|
||||
ServiceError *ServiceError `json:"error"`
|
||||
}
|
||||
re := respErr{}
|
||||
defer pt.resp.Body.Close()
|
||||
var b []byte
|
||||
b, err = ioutil.ReadAll(pt.resp.Body)
|
||||
if err != nil {
|
||||
goto Default
|
||||
}
|
||||
if err = json.Unmarshal(b, &re); err != nil {
|
||||
goto Default
|
||||
}
|
||||
// unmarshalling the error didn't yield anything, try unwrapped error
|
||||
if re.ServiceError == nil {
|
||||
err = json.Unmarshal(b, &re.ServiceError)
|
||||
if err != nil {
|
||||
goto Default
|
||||
}
|
||||
}
|
||||
if re.ServiceError != nil {
|
||||
pt.Err = re.ServiceError
|
||||
return
|
||||
}
|
||||
}
|
||||
Default:
|
||||
se := &ServiceError{
|
||||
Code: fmt.Sprintf("HTTP status code %v", pt.resp.StatusCode),
|
||||
Message: pt.resp.Status,
|
||||
}
|
||||
if err != nil {
|
||||
se.InnerError = make(map[string]interface{})
|
||||
se.InnerError["unmarshalError"] = err.Error()
|
||||
}
|
||||
pt.Err = se
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerBase) updatePollingState(provStateApl bool) error {
|
||||
if pt.Pm == PollingAsyncOperation && pt.rawBody["status"] != nil {
|
||||
pt.State = pt.rawBody["status"].(string)
|
||||
} else {
|
||||
if pt.resp.StatusCode == http.StatusAccepted {
|
||||
pt.State = operationInProgress
|
||||
} else if provStateApl {
|
||||
if ps := pt.getProvisioningState(); ps != nil {
|
||||
pt.State = *ps
|
||||
} else {
|
||||
pt.State = operationSucceeded
|
||||
}
|
||||
} else {
|
||||
return autorest.NewError("pollingTrackerBase", "updatePollingState", "the response from the async operation has an invalid status code")
|
||||
}
|
||||
}
|
||||
// if the operation has failed update the error state
|
||||
if pt.hasFailed() {
|
||||
pt.updateErrorFromResponse()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) pollingError() error {
|
||||
if pt.Err == nil {
|
||||
return nil
|
||||
}
|
||||
return pt.Err
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) pollingMethod() PollingMethodType {
|
||||
return pt.Pm
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) pollingStatus() string {
|
||||
return pt.State
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) pollingURL() string {
|
||||
return pt.URI
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) finalGetURL() string {
|
||||
return pt.FinalGetURI
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) hasTerminated() bool {
|
||||
return strings.EqualFold(pt.State, operationCanceled) || strings.EqualFold(pt.State, operationFailed) || strings.EqualFold(pt.State, operationSucceeded)
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) hasFailed() bool {
|
||||
return strings.EqualFold(pt.State, operationCanceled) || strings.EqualFold(pt.State, operationFailed)
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) hasSucceeded() bool {
|
||||
return strings.EqualFold(pt.State, operationSucceeded)
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) latestResponse() *http.Response {
|
||||
return pt.resp
|
||||
}
|
||||
|
||||
// error checking common to all trackers
|
||||
func (pt pollingTrackerBase) baseCheckForErrors() error {
|
||||
// for Azure-AsyncOperations the response body cannot be nil or empty
|
||||
if pt.Pm == PollingAsyncOperation {
|
||||
if pt.resp.Body == nil || pt.resp.ContentLength == 0 {
|
||||
return autorest.NewError("pollingTrackerBase", "baseCheckForErrors", "for Azure-AsyncOperation response body cannot be nil")
|
||||
}
|
||||
if pt.rawBody["status"] == nil {
|
||||
return autorest.NewError("pollingTrackerBase", "baseCheckForErrors", "missing status property in Azure-AsyncOperation response body")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DELETE
|
||||
|
||||
type pollingTrackerDelete struct {
|
||||
pollingTrackerBase
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerDelete) updateHeaders() error {
|
||||
// for 201 the Location header is required
|
||||
if pt.resp.StatusCode == http.StatusCreated {
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
|
||||
return err
|
||||
} else if lh == "" {
|
||||
return autorest.NewError("pollingTrackerDelete", "updateHeaders", "missing Location header in 201 response")
|
||||
} else {
|
||||
pt.URI = lh
|
||||
}
|
||||
pt.Pm = PollingLocation
|
||||
pt.FinalGetURI = pt.URI
|
||||
}
|
||||
// for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
|
||||
if pt.resp.StatusCode == http.StatusAccepted {
|
||||
ao, err := getURLFromAsyncOpHeader(pt.resp)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ao != "" {
|
||||
pt.URI = ao
|
||||
pt.Pm = PollingAsyncOperation
|
||||
}
|
||||
// if the Location header is invalid and we already have a polling URL
|
||||
// then we don't care if the Location header URL is malformed.
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
|
||||
return err
|
||||
} else if lh != "" {
|
||||
if ao == "" {
|
||||
pt.URI = lh
|
||||
pt.Pm = PollingLocation
|
||||
}
|
||||
// when both headers are returned we use the value in the Location header for the final GET
|
||||
pt.FinalGetURI = lh
|
||||
}
|
||||
// make sure a polling URL was found
|
||||
if pt.URI == "" {
|
||||
return autorest.NewError("pollingTrackerPost", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pollingTrackerDelete) checkForErrors() error {
|
||||
return pt.baseCheckForErrors()
|
||||
}
|
||||
|
||||
func (pt pollingTrackerDelete) provisioningStateApplicable() bool {
|
||||
return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusNoContent
|
||||
}
|
||||
|
||||
// PATCH
|
||||
|
||||
type pollingTrackerPatch struct {
|
||||
pollingTrackerBase
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerPatch) updateHeaders() error {
|
||||
// by default we can use the original URL for polling and final GET
|
||||
if pt.URI == "" {
|
||||
pt.URI = pt.resp.Request.URL.String()
|
||||
}
|
||||
if pt.FinalGetURI == "" {
|
||||
pt.FinalGetURI = pt.resp.Request.URL.String()
|
||||
}
|
||||
if pt.Pm == PollingUnknown {
|
||||
pt.Pm = PollingRequestURI
|
||||
}
|
||||
// for 201 it's permissible for no headers to be returned
|
||||
if pt.resp.StatusCode == http.StatusCreated {
|
||||
if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
|
||||
return err
|
||||
} else if ao != "" {
|
||||
pt.URI = ao
|
||||
pt.Pm = PollingAsyncOperation
|
||||
}
|
||||
}
|
||||
// for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
|
||||
// note the absense of the "final GET" mechanism for PATCH
|
||||
if pt.resp.StatusCode == http.StatusAccepted {
|
||||
ao, err := getURLFromAsyncOpHeader(pt.resp)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ao != "" {
|
||||
pt.URI = ao
|
||||
pt.Pm = PollingAsyncOperation
|
||||
}
|
||||
if ao == "" {
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
|
||||
return err
|
||||
} else if lh == "" {
|
||||
return autorest.NewError("pollingTrackerPatch", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
|
||||
} else {
|
||||
pt.URI = lh
|
||||
pt.Pm = PollingLocation
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pollingTrackerPatch) checkForErrors() error {
|
||||
return pt.baseCheckForErrors()
|
||||
}
|
||||
|
||||
func (pt pollingTrackerPatch) provisioningStateApplicable() bool {
|
||||
return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusCreated
|
||||
}
|
||||
|
||||
// POST
|
||||
|
||||
type pollingTrackerPost struct {
|
||||
pollingTrackerBase
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerPost) updateHeaders() error {
|
||||
// 201 requires Location header
|
||||
if pt.resp.StatusCode == http.StatusCreated {
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
|
||||
return err
|
||||
} else if lh == "" {
|
||||
return autorest.NewError("pollingTrackerPost", "updateHeaders", "missing Location header in 201 response")
|
||||
} else {
|
||||
pt.URI = lh
|
||||
pt.FinalGetURI = lh
|
||||
pt.Pm = PollingLocation
|
||||
}
|
||||
}
|
||||
// for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
|
||||
if pt.resp.StatusCode == http.StatusAccepted {
|
||||
ao, err := getURLFromAsyncOpHeader(pt.resp)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ao != "" {
|
||||
pt.URI = ao
|
||||
pt.Pm = PollingAsyncOperation
|
||||
}
|
||||
// if the Location header is invalid and we already have a polling URL
|
||||
// then we don't care if the Location header URL is malformed.
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
|
||||
return err
|
||||
} else if lh != "" {
|
||||
if ao == "" {
|
||||
pt.URI = lh
|
||||
pt.Pm = PollingLocation
|
||||
}
|
||||
// when both headers are returned we use the value in the Location header for the final GET
|
||||
pt.FinalGetURI = lh
|
||||
}
|
||||
// make sure a polling URL was found
|
||||
if pt.URI == "" {
|
||||
return autorest.NewError("pollingTrackerPost", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pollingTrackerPost) checkForErrors() error {
|
||||
return pt.baseCheckForErrors()
|
||||
}
|
||||
|
||||
func (pt pollingTrackerPost) provisioningStateApplicable() bool {
|
||||
return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusNoContent
|
||||
}
|
||||
|
||||
// PUT
|
||||
|
||||
type pollingTrackerPut struct {
|
||||
pollingTrackerBase
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerPut) updateHeaders() error {
|
||||
// by default we can use the original URL for polling and final GET
|
||||
if pt.URI == "" {
|
||||
pt.URI = pt.resp.Request.URL.String()
|
||||
}
|
||||
if pt.FinalGetURI == "" {
|
||||
pt.FinalGetURI = pt.resp.Request.URL.String()
|
||||
}
|
||||
if pt.Pm == PollingUnknown {
|
||||
pt.Pm = PollingRequestURI
|
||||
}
|
||||
// for 201 it's permissible for no headers to be returned
|
||||
if pt.resp.StatusCode == http.StatusCreated {
|
||||
if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
|
||||
return err
|
||||
} else if ao != "" {
|
||||
pt.URI = ao
|
||||
pt.Pm = PollingAsyncOperation
|
||||
}
|
||||
}
|
||||
// for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
|
||||
if pt.resp.StatusCode == http.StatusAccepted {
|
||||
ao, err := getURLFromAsyncOpHeader(pt.resp)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ao != "" {
|
||||
pt.URI = ao
|
||||
pt.Pm = PollingAsyncOperation
|
||||
}
|
||||
// if the Location header is invalid and we already have a polling URL
|
||||
// then we don't care if the Location header URL is malformed.
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
|
||||
return err
|
||||
} else if lh != "" {
|
||||
if ao == "" {
|
||||
pt.URI = lh
|
||||
pt.Pm = PollingLocation
|
||||
}
|
||||
// when both headers are returned we use the value in the Location header for the final GET
|
||||
pt.FinalGetURI = lh
|
||||
}
|
||||
// make sure a polling URL was found
|
||||
if pt.URI == "" {
|
||||
return autorest.NewError("pollingTrackerPut", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pollingTrackerPut) checkForErrors() error {
|
||||
err := pt.baseCheckForErrors()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if there are no LRO headers then the body cannot be empty
|
||||
ao, err := getURLFromAsyncOpHeader(pt.resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lh, err := getURLFromLocationHeader(pt.resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ao == "" && lh == "" && len(pt.rawBody) == 0 {
|
||||
return autorest.NewError("pollingTrackerPut", "checkForErrors", "the response did not contain a body")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pollingTrackerPut) provisioningStateApplicable() bool {
|
||||
return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusCreated
|
||||
}
|
||||
|
||||
// creates a polling tracker based on the verb of the original request
|
||||
func createPollingTracker(resp *http.Response) (pollingTracker, error) {
|
||||
var pt pollingTracker
|
||||
switch strings.ToUpper(resp.Request.Method) {
|
||||
case http.MethodDelete:
|
||||
pt = &pollingTrackerDelete{pollingTrackerBase: pollingTrackerBase{resp: resp}}
|
||||
case http.MethodPatch:
|
||||
pt = &pollingTrackerPatch{pollingTrackerBase: pollingTrackerBase{resp: resp}}
|
||||
case http.MethodPost:
|
||||
pt = &pollingTrackerPost{pollingTrackerBase: pollingTrackerBase{resp: resp}}
|
||||
case http.MethodPut:
|
||||
pt = &pollingTrackerPut{pollingTrackerBase: pollingTrackerBase{resp: resp}}
|
||||
default:
|
||||
return nil, autorest.NewError("azure", "createPollingTracker", "unsupported HTTP method %s", resp.Request.Method)
|
||||
}
|
||||
if err := pt.initializeState(); err != nil {
|
||||
return pt, err
|
||||
}
|
||||
// this initializes the polling header values, we do this during creation in case the
|
||||
// initial response send us invalid values; this way the API call will return a non-nil
|
||||
// error (not doing this means the error shows up in Future.Done)
|
||||
return pt, pt.updateHeaders()
|
||||
}
|
||||
|
||||
// gets the polling URL from the Azure-AsyncOperation header.
|
||||
// ensures the URL is well-formed and absolute.
|
||||
func getURLFromAsyncOpHeader(resp *http.Response) (string, error) {
|
||||
s := resp.Header.Get(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
if s == "" {
|
||||
return "", nil
|
||||
}
|
||||
if !isValidURL(s) {
|
||||
return "", autorest.NewError("azure", "getURLFromAsyncOpHeader", "invalid polling URL '%s'", s)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// gets the polling URL from the Location header.
|
||||
// ensures the URL is well-formed and absolute.
|
||||
func getURLFromLocationHeader(resp *http.Response) (string, error) {
|
||||
s := resp.Header.Get(http.CanonicalHeaderKey(autorest.HeaderLocation))
|
||||
if s == "" {
|
||||
return "", nil
|
||||
}
|
||||
if !isValidURL(s) {
|
||||
return "", autorest.NewError("azure", "getURLFromLocationHeader", "invalid polling URL '%s'", s)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// verify that the URL is valid and absolute
|
||||
func isValidURL(s string) bool {
|
||||
u, err := url.Parse(s)
|
||||
return err == nil && u.IsAbs()
|
||||
}
|
||||
|
||||
// DoPollForAsynchronous returns a SendDecorator that polls if the http.Response is for an Azure
|
||||
// long-running operation. It will delay between requests for the duration specified in the
|
||||
// RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled via
|
||||
// the context associated with the http.Request.
|
||||
// Deprecated: Prefer using Futures to allow for non-blocking async operations.
|
||||
func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator {
|
||||
return func(s autorest.Sender) autorest.Sender {
|
||||
return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
resp, err := s.Do(r)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if !autorest.ResponseHasStatusCode(resp, pollingCodes[:]...) {
|
||||
return resp, nil
|
||||
}
|
||||
future, err := NewFutureFromResponse(resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
// retry until either the LRO completes or we receive an error
|
||||
var done bool
|
||||
for done, err = future.Done(s); !done && err == nil; done, err = future.Done(s) {
|
||||
// check for Retry-After delay, if not present use the specified polling delay
|
||||
if pd, ok := future.GetPollingDelay(); ok {
|
||||
delay = pd
|
||||
}
|
||||
// wait until the delay elapses or the context is cancelled
|
||||
if delayElapsed := autorest.DelayForBackoff(delay, 0, r.Context().Done()); !delayElapsed {
|
||||
return future.Response(),
|
||||
autorest.NewErrorWithError(r.Context().Err(), "azure", "DoPollForAsynchronous", future.Response(), "context has been cancelled")
|
||||
}
|
||||
}
|
||||
return future.Response(), err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// PollingMethodType defines a type used for enumerating polling mechanisms.
|
||||
type PollingMethodType string
|
||||
|
||||
const (
|
||||
// PollingAsyncOperation indicates the polling method uses the Azure-AsyncOperation header.
|
||||
PollingAsyncOperation PollingMethodType = "AsyncOperation"
|
||||
|
||||
// PollingLocation indicates the polling method uses the Location header.
|
||||
PollingLocation PollingMethodType = "Location"
|
||||
|
||||
// PollingRequestURI indicates the polling method uses the original request URI.
|
||||
PollingRequestURI PollingMethodType = "RequestURI"
|
||||
|
||||
// PollingUnknown indicates an unknown polling method and is the default value.
|
||||
PollingUnknown PollingMethodType = ""
|
||||
)
|
||||
|
||||
// AsyncOpIncompleteError is the type that's returned from a future that has not completed.
|
||||
type AsyncOpIncompleteError struct {
|
||||
// FutureType is the name of the type composed of a azure.Future.
|
||||
FutureType string
|
||||
}
|
||||
|
||||
// Error returns an error message including the originating type name of the error.
|
||||
func (e AsyncOpIncompleteError) Error() string {
|
||||
return fmt.Sprintf("%s: asynchronous operation has not completed", e.FutureType)
|
||||
}
|
||||
|
||||
// NewAsyncOpIncompleteError creates a new AsyncOpIncompleteError with the specified parameters.
|
||||
func NewAsyncOpIncompleteError(futureType string) AsyncOpIncompleteError {
|
||||
return AsyncOpIncompleteError{
|
||||
FutureType: futureType,
|
||||
}
|
||||
}
|
||||
326
vendor/github.com/Azure/go-autorest/autorest/azure/azure.go
generated
vendored
326
vendor/github.com/Azure/go-autorest/autorest/azure/azure.go
generated
vendored
@@ -1,326 +0,0 @@
|
||||
// Package azure provides Azure-specific implementations used with AutoRest.
|
||||
// See the included examples for more detail.
|
||||
package azure
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
)
|
||||
|
||||
const (
|
||||
// HeaderClientID is the Azure extension header to set a user-specified request ID.
|
||||
HeaderClientID = "x-ms-client-request-id"
|
||||
|
||||
// HeaderReturnClientID is the Azure extension header to set if the user-specified request ID
|
||||
// should be included in the response.
|
||||
HeaderReturnClientID = "x-ms-return-client-request-id"
|
||||
|
||||
// HeaderRequestID is the Azure extension header of the service generated request ID returned
|
||||
// in the response.
|
||||
HeaderRequestID = "x-ms-request-id"
|
||||
)
|
||||
|
||||
// ServiceError encapsulates the error response from an Azure service.
|
||||
// It adhears to the OData v4 specification for error responses.
|
||||
type ServiceError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details []map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
|
||||
}
|
||||
|
||||
func (se ServiceError) Error() string {
|
||||
result := fmt.Sprintf("Code=%q Message=%q", se.Code, se.Message)
|
||||
|
||||
if se.Target != nil {
|
||||
result += fmt.Sprintf(" Target=%q", *se.Target)
|
||||
}
|
||||
|
||||
if se.Details != nil {
|
||||
d, err := json.Marshal(se.Details)
|
||||
if err != nil {
|
||||
result += fmt.Sprintf(" Details=%v", se.Details)
|
||||
}
|
||||
result += fmt.Sprintf(" Details=%v", string(d))
|
||||
}
|
||||
|
||||
if se.InnerError != nil {
|
||||
d, err := json.Marshal(se.InnerError)
|
||||
if err != nil {
|
||||
result += fmt.Sprintf(" InnerError=%v", se.InnerError)
|
||||
}
|
||||
result += fmt.Sprintf(" InnerError=%v", string(d))
|
||||
}
|
||||
|
||||
if se.AdditionalInfo != nil {
|
||||
d, err := json.Marshal(se.AdditionalInfo)
|
||||
if err != nil {
|
||||
result += fmt.Sprintf(" AdditionalInfo=%v", se.AdditionalInfo)
|
||||
}
|
||||
result += fmt.Sprintf(" AdditionalInfo=%v", string(d))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for the ServiceError type.
|
||||
func (se *ServiceError) UnmarshalJSON(b []byte) error {
|
||||
// per the OData v4 spec the details field must be an array of JSON objects.
|
||||
// unfortunately not all services adhear to the spec and just return a single
|
||||
// object instead of an array with one object. so we have to perform some
|
||||
// shenanigans to accommodate both cases.
|
||||
// http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091
|
||||
|
||||
type serviceError1 struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details []map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
|
||||
}
|
||||
|
||||
type serviceError2 struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Target *string `json:"target"`
|
||||
Details map[string]interface{} `json:"details"`
|
||||
InnerError map[string]interface{} `json:"innererror"`
|
||||
AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
|
||||
}
|
||||
|
||||
se1 := serviceError1{}
|
||||
err := json.Unmarshal(b, &se1)
|
||||
if err == nil {
|
||||
se.populate(se1.Code, se1.Message, se1.Target, se1.Details, se1.InnerError, se1.AdditionalInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
se2 := serviceError2{}
|
||||
err = json.Unmarshal(b, &se2)
|
||||
if err == nil {
|
||||
se.populate(se2.Code, se2.Message, se2.Target, nil, se2.InnerError, se2.AdditionalInfo)
|
||||
se.Details = append(se.Details, se2.Details)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (se *ServiceError) populate(code, message string, target *string, details []map[string]interface{}, inner map[string]interface{}, additional []map[string]interface{}) {
|
||||
se.Code = code
|
||||
se.Message = message
|
||||
se.Target = target
|
||||
se.Details = details
|
||||
se.InnerError = inner
|
||||
se.AdditionalInfo = additional
|
||||
}
|
||||
|
||||
// RequestError describes an error response returned by Azure service.
|
||||
type RequestError struct {
|
||||
autorest.DetailedError
|
||||
|
||||
// The error returned by the Azure service.
|
||||
ServiceError *ServiceError `json:"error"`
|
||||
|
||||
// The request id (from the x-ms-request-id-header) of the request.
|
||||
RequestID string
|
||||
}
|
||||
|
||||
// Error returns a human-friendly error message from service error.
|
||||
func (e RequestError) Error() string {
|
||||
return fmt.Sprintf("autorest/azure: Service returned an error. Status=%v %v",
|
||||
e.StatusCode, e.ServiceError)
|
||||
}
|
||||
|
||||
// IsAzureError returns true if the passed error is an Azure Service error; false otherwise.
|
||||
func IsAzureError(e error) bool {
|
||||
_, ok := e.(*RequestError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Resource contains details about an Azure resource.
|
||||
type Resource struct {
|
||||
SubscriptionID string
|
||||
ResourceGroup string
|
||||
Provider string
|
||||
ResourceType string
|
||||
ResourceName string
|
||||
}
|
||||
|
||||
// ParseResourceID parses a resource ID into a ResourceDetails struct.
|
||||
// See https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-resource#return-value-4.
|
||||
func ParseResourceID(resourceID string) (Resource, error) {
|
||||
|
||||
const resourceIDPatternText = `(?i)subscriptions/(.+)/resourceGroups/(.+)/providers/(.+?)/(.+?)/(.+)`
|
||||
resourceIDPattern := regexp.MustCompile(resourceIDPatternText)
|
||||
match := resourceIDPattern.FindStringSubmatch(resourceID)
|
||||
|
||||
if len(match) == 0 {
|
||||
return Resource{}, fmt.Errorf("parsing failed for %s. Invalid resource Id format", resourceID)
|
||||
}
|
||||
|
||||
v := strings.Split(match[5], "/")
|
||||
resourceName := v[len(v)-1]
|
||||
|
||||
result := Resource{
|
||||
SubscriptionID: match[1],
|
||||
ResourceGroup: match[2],
|
||||
Provider: match[3],
|
||||
ResourceType: match[4],
|
||||
ResourceName: resourceName,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// NewErrorWithError creates a new Error conforming object from the
|
||||
// passed packageType, method, statusCode of the given resp (UndefinedStatusCode
|
||||
// if resp is nil), message, and original error. message is treated as a format
|
||||
// string to which the optional args apply.
|
||||
func NewErrorWithError(original error, packageType string, method string, resp *http.Response, message string, args ...interface{}) RequestError {
|
||||
if v, ok := original.(*RequestError); ok {
|
||||
return *v
|
||||
}
|
||||
|
||||
statusCode := autorest.UndefinedStatusCode
|
||||
if resp != nil {
|
||||
statusCode = resp.StatusCode
|
||||
}
|
||||
return RequestError{
|
||||
DetailedError: autorest.DetailedError{
|
||||
Original: original,
|
||||
PackageType: packageType,
|
||||
Method: method,
|
||||
StatusCode: statusCode,
|
||||
Message: fmt.Sprintf(message, args...),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithReturningClientID returns a PrepareDecorator that adds an HTTP extension header of
|
||||
// x-ms-client-request-id whose value is the passed, undecorated UUID (e.g.,
|
||||
// "0F39878C-5F76-4DB8-A25D-61D2C193C3CA"). It also sets the x-ms-return-client-request-id
|
||||
// header to true such that UUID accompanies the http.Response.
|
||||
func WithReturningClientID(uuid string) autorest.PrepareDecorator {
|
||||
preparer := autorest.CreatePreparer(
|
||||
WithClientID(uuid),
|
||||
WithReturnClientID(true))
|
||||
|
||||
return func(p autorest.Preparer) autorest.Preparer {
|
||||
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return preparer.Prepare(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientID returns a PrepareDecorator that adds an HTTP extension header of
|
||||
// x-ms-client-request-id whose value is passed, undecorated UUID (e.g.,
|
||||
// "0F39878C-5F76-4DB8-A25D-61D2C193C3CA").
|
||||
func WithClientID(uuid string) autorest.PrepareDecorator {
|
||||
return autorest.WithHeader(HeaderClientID, uuid)
|
||||
}
|
||||
|
||||
// WithReturnClientID returns a PrepareDecorator that adds an HTTP extension header of
|
||||
// x-ms-return-client-request-id whose boolean value indicates if the value of the
|
||||
// x-ms-client-request-id header should be included in the http.Response.
|
||||
func WithReturnClientID(b bool) autorest.PrepareDecorator {
|
||||
return autorest.WithHeader(HeaderReturnClientID, strconv.FormatBool(b))
|
||||
}
|
||||
|
||||
// ExtractClientID extracts the client identifier from the x-ms-client-request-id header set on the
|
||||
// http.Request sent to the service (and returned in the http.Response)
|
||||
func ExtractClientID(resp *http.Response) string {
|
||||
return autorest.ExtractHeaderValue(HeaderClientID, resp)
|
||||
}
|
||||
|
||||
// ExtractRequestID extracts the Azure server generated request identifier from the
|
||||
// x-ms-request-id header.
|
||||
func ExtractRequestID(resp *http.Response) string {
|
||||
return autorest.ExtractHeaderValue(HeaderRequestID, resp)
|
||||
}
|
||||
|
||||
// WithErrorUnlessStatusCode returns a RespondDecorator that emits an
|
||||
// azure.RequestError by reading the response body unless the response HTTP status code
|
||||
// is among the set passed.
|
||||
//
|
||||
// If there is a chance service may return responses other than the Azure error
|
||||
// format and the response cannot be parsed into an error, a decoding error will
|
||||
// be returned containing the response body. In any case, the Responder will
|
||||
// return an error if the status code is not satisfied.
|
||||
//
|
||||
// If this Responder returns an error, the response body will be replaced with
|
||||
// an in-memory reader, which needs no further closing.
|
||||
func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
|
||||
return func(r autorest.Responder) autorest.Responder {
|
||||
return autorest.ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if err == nil && !autorest.ResponseHasStatusCode(resp, codes...) {
|
||||
var e RequestError
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Copy and replace the Body in case it does not contain an error object.
|
||||
// This will leave the Body available to the caller.
|
||||
b, decodeErr := autorest.CopyAndDecode(autorest.EncodedAsJSON, resp.Body, &e)
|
||||
resp.Body = ioutil.NopCloser(&b)
|
||||
if decodeErr != nil {
|
||||
return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), decodeErr)
|
||||
}
|
||||
if e.ServiceError == nil {
|
||||
// Check if error is unwrapped ServiceError
|
||||
if err := json.Unmarshal(b.Bytes(), &e.ServiceError); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if e.ServiceError.Message == "" {
|
||||
// if we're here it means the returned error wasn't OData v4 compliant.
|
||||
// try to unmarshal the body as raw JSON in hopes of getting something.
|
||||
rawBody := map[string]interface{}{}
|
||||
if err := json.Unmarshal(b.Bytes(), &rawBody); err != nil {
|
||||
return err
|
||||
}
|
||||
e.ServiceError = &ServiceError{
|
||||
Code: "Unknown",
|
||||
Message: "Unknown service error",
|
||||
}
|
||||
if len(rawBody) > 0 {
|
||||
e.ServiceError.Details = []map[string]interface{}{rawBody}
|
||||
}
|
||||
}
|
||||
e.Response = resp
|
||||
e.RequestID = ExtractRequestID(resp)
|
||||
if e.StatusCode == nil {
|
||||
e.StatusCode = resp.StatusCode
|
||||
}
|
||||
err = &e
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
191
vendor/github.com/Azure/go-autorest/autorest/azure/environments.go
generated
vendored
191
vendor/github.com/Azure/go-autorest/autorest/azure/environments.go
generated
vendored
@@ -1,191 +0,0 @@
|
||||
package azure
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnvironmentFilepathName captures the name of the environment variable containing the path to the file
|
||||
// to be used while populating the Azure Environment.
|
||||
const EnvironmentFilepathName = "AZURE_ENVIRONMENT_FILEPATH"
|
||||
|
||||
var environments = map[string]Environment{
|
||||
"AZURECHINACLOUD": ChinaCloud,
|
||||
"AZUREGERMANCLOUD": GermanCloud,
|
||||
"AZUREPUBLICCLOUD": PublicCloud,
|
||||
"AZUREUSGOVERNMENTCLOUD": USGovernmentCloud,
|
||||
}
|
||||
|
||||
// Environment represents a set of endpoints for each of Azure's Clouds.
|
||||
type Environment struct {
|
||||
Name string `json:"name"`
|
||||
ManagementPortalURL string `json:"managementPortalURL"`
|
||||
PublishSettingsURL string `json:"publishSettingsURL"`
|
||||
ServiceManagementEndpoint string `json:"serviceManagementEndpoint"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpoint"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpoint"`
|
||||
GalleryEndpoint string `json:"galleryEndpoint"`
|
||||
KeyVaultEndpoint string `json:"keyVaultEndpoint"`
|
||||
GraphEndpoint string `json:"graphEndpoint"`
|
||||
ServiceBusEndpoint string `json:"serviceBusEndpoint"`
|
||||
BatchManagementEndpoint string `json:"batchManagementEndpoint"`
|
||||
StorageEndpointSuffix string `json:"storageEndpointSuffix"`
|
||||
SQLDatabaseDNSSuffix string `json:"sqlDatabaseDNSSuffix"`
|
||||
TrafficManagerDNSSuffix string `json:"trafficManagerDNSSuffix"`
|
||||
KeyVaultDNSSuffix string `json:"keyVaultDNSSuffix"`
|
||||
ServiceBusEndpointSuffix string `json:"serviceBusEndpointSuffix"`
|
||||
ServiceManagementVMDNSSuffix string `json:"serviceManagementVMDNSSuffix"`
|
||||
ResourceManagerVMDNSSuffix string `json:"resourceManagerVMDNSSuffix"`
|
||||
ContainerRegistryDNSSuffix string `json:"containerRegistryDNSSuffix"`
|
||||
TokenAudience string `json:"tokenAudience"`
|
||||
}
|
||||
|
||||
var (
|
||||
// PublicCloud is the default public Azure cloud environment
|
||||
PublicCloud = Environment{
|
||||
Name: "AzurePublicCloud",
|
||||
ManagementPortalURL: "https://manage.windowsazure.com/",
|
||||
PublishSettingsURL: "https://manage.windowsazure.com/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.windows.net/",
|
||||
ResourceManagerEndpoint: "https://management.azure.com/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
|
||||
GalleryEndpoint: "https://gallery.azure.com/",
|
||||
KeyVaultEndpoint: "https://vault.azure.net/",
|
||||
GraphEndpoint: "https://graph.windows.net/",
|
||||
ServiceBusEndpoint: "https://servicebus.windows.net/",
|
||||
BatchManagementEndpoint: "https://batch.core.windows.net/",
|
||||
StorageEndpointSuffix: "core.windows.net",
|
||||
SQLDatabaseDNSSuffix: "database.windows.net",
|
||||
TrafficManagerDNSSuffix: "trafficmanager.net",
|
||||
KeyVaultDNSSuffix: "vault.azure.net",
|
||||
ServiceBusEndpointSuffix: "servicebus.windows.net",
|
||||
ServiceManagementVMDNSSuffix: "cloudapp.net",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.azure.com",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
TokenAudience: "https://management.azure.com/",
|
||||
}
|
||||
|
||||
// USGovernmentCloud is the cloud environment for the US Government
|
||||
USGovernmentCloud = Environment{
|
||||
Name: "AzureUSGovernmentCloud",
|
||||
ManagementPortalURL: "https://manage.windowsazure.us/",
|
||||
PublishSettingsURL: "https://manage.windowsazure.us/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.usgovcloudapi.net/",
|
||||
ResourceManagerEndpoint: "https://management.usgovcloudapi.net/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.us/",
|
||||
GalleryEndpoint: "https://gallery.usgovcloudapi.net/",
|
||||
KeyVaultEndpoint: "https://vault.usgovcloudapi.net/",
|
||||
GraphEndpoint: "https://graph.windows.net/",
|
||||
ServiceBusEndpoint: "https://servicebus.usgovcloudapi.net/",
|
||||
BatchManagementEndpoint: "https://batch.core.usgovcloudapi.net/",
|
||||
StorageEndpointSuffix: "core.usgovcloudapi.net",
|
||||
SQLDatabaseDNSSuffix: "database.usgovcloudapi.net",
|
||||
TrafficManagerDNSSuffix: "usgovtrafficmanager.net",
|
||||
KeyVaultDNSSuffix: "vault.usgovcloudapi.net",
|
||||
ServiceBusEndpointSuffix: "servicebus.usgovcloudapi.net",
|
||||
ServiceManagementVMDNSSuffix: "usgovcloudapp.net",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.windowsazure.us",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
TokenAudience: "https://management.usgovcloudapi.net/",
|
||||
}
|
||||
|
||||
// ChinaCloud is the cloud environment operated in China
|
||||
ChinaCloud = Environment{
|
||||
Name: "AzureChinaCloud",
|
||||
ManagementPortalURL: "https://manage.chinacloudapi.com/",
|
||||
PublishSettingsURL: "https://manage.chinacloudapi.com/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.chinacloudapi.cn/",
|
||||
ResourceManagerEndpoint: "https://management.chinacloudapi.cn/",
|
||||
ActiveDirectoryEndpoint: "https://login.chinacloudapi.cn/",
|
||||
GalleryEndpoint: "https://gallery.chinacloudapi.cn/",
|
||||
KeyVaultEndpoint: "https://vault.azure.cn/",
|
||||
GraphEndpoint: "https://graph.chinacloudapi.cn/",
|
||||
ServiceBusEndpoint: "https://servicebus.chinacloudapi.cn/",
|
||||
BatchManagementEndpoint: "https://batch.chinacloudapi.cn/",
|
||||
StorageEndpointSuffix: "core.chinacloudapi.cn",
|
||||
SQLDatabaseDNSSuffix: "database.chinacloudapi.cn",
|
||||
TrafficManagerDNSSuffix: "trafficmanager.cn",
|
||||
KeyVaultDNSSuffix: "vault.azure.cn",
|
||||
ServiceBusEndpointSuffix: "servicebus.chinacloudapi.cn",
|
||||
ServiceManagementVMDNSSuffix: "chinacloudapp.cn",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.azure.cn",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
TokenAudience: "https://management.chinacloudapi.cn/",
|
||||
}
|
||||
|
||||
// GermanCloud is the cloud environment operated in Germany
|
||||
GermanCloud = Environment{
|
||||
Name: "AzureGermanCloud",
|
||||
ManagementPortalURL: "http://portal.microsoftazure.de/",
|
||||
PublishSettingsURL: "https://manage.microsoftazure.de/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.cloudapi.de/",
|
||||
ResourceManagerEndpoint: "https://management.microsoftazure.de/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.de/",
|
||||
GalleryEndpoint: "https://gallery.cloudapi.de/",
|
||||
KeyVaultEndpoint: "https://vault.microsoftazure.de/",
|
||||
GraphEndpoint: "https://graph.cloudapi.de/",
|
||||
ServiceBusEndpoint: "https://servicebus.cloudapi.de/",
|
||||
BatchManagementEndpoint: "https://batch.cloudapi.de/",
|
||||
StorageEndpointSuffix: "core.cloudapi.de",
|
||||
SQLDatabaseDNSSuffix: "database.cloudapi.de",
|
||||
TrafficManagerDNSSuffix: "azuretrafficmanager.de",
|
||||
KeyVaultDNSSuffix: "vault.microsoftazure.de",
|
||||
ServiceBusEndpointSuffix: "servicebus.cloudapi.de",
|
||||
ServiceManagementVMDNSSuffix: "azurecloudapp.de",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.microsoftazure.de",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
TokenAudience: "https://management.microsoftazure.de/",
|
||||
}
|
||||
)
|
||||
|
||||
// EnvironmentFromName returns an Environment based on the common name specified.
|
||||
func EnvironmentFromName(name string) (Environment, error) {
|
||||
// IMPORTANT
|
||||
// As per @radhikagupta5:
|
||||
// This is technical debt, fundamentally here because Kubernetes is not currently accepting
|
||||
// contributions to the providers. Once that is an option, the provider should be updated to
|
||||
// directly call `EnvironmentFromFile`. Until then, we rely on dispatching Azure Stack environment creation
|
||||
// from this method based on the name that is provided to us.
|
||||
if strings.EqualFold(name, "AZURESTACKCLOUD") {
|
||||
return EnvironmentFromFile(os.Getenv(EnvironmentFilepathName))
|
||||
}
|
||||
|
||||
name = strings.ToUpper(name)
|
||||
env, ok := environments[name]
|
||||
if !ok {
|
||||
return env, fmt.Errorf("autorest/azure: There is no cloud environment matching the name %q", name)
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// EnvironmentFromFile loads an Environment from a configuration file available on disk.
|
||||
// This function is particularly useful in the Hybrid Cloud model, where one must define their own
|
||||
// endpoints.
|
||||
func EnvironmentFromFile(location string) (unmarshaled Environment, err error) {
|
||||
fileContents, err := ioutil.ReadFile(location)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileContents, &unmarshaled)
|
||||
|
||||
return
|
||||
}
|
||||
245
vendor/github.com/Azure/go-autorest/autorest/azure/metadata_environment.go
generated
vendored
245
vendor/github.com/Azure/go-autorest/autorest/azure/metadata_environment.go
generated
vendored
@@ -1,245 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
)
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
type audience []string
|
||||
|
||||
type authentication struct {
|
||||
LoginEndpoint string `json:"loginEndpoint"`
|
||||
Audiences audience `json:"audiences"`
|
||||
}
|
||||
|
||||
type environmentMetadataInfo struct {
|
||||
GalleryEndpoint string `json:"galleryEndpoint"`
|
||||
GraphEndpoint string `json:"graphEndpoint"`
|
||||
PortalEndpoint string `json:"portalEndpoint"`
|
||||
Authentication authentication `json:"authentication"`
|
||||
}
|
||||
|
||||
// EnvironmentProperty represent property names that clients can override
|
||||
type EnvironmentProperty string
|
||||
|
||||
const (
|
||||
// EnvironmentName ...
|
||||
EnvironmentName EnvironmentProperty = "name"
|
||||
// EnvironmentManagementPortalURL ..
|
||||
EnvironmentManagementPortalURL EnvironmentProperty = "managementPortalURL"
|
||||
// EnvironmentPublishSettingsURL ...
|
||||
EnvironmentPublishSettingsURL EnvironmentProperty = "publishSettingsURL"
|
||||
// EnvironmentServiceManagementEndpoint ...
|
||||
EnvironmentServiceManagementEndpoint EnvironmentProperty = "serviceManagementEndpoint"
|
||||
// EnvironmentResourceManagerEndpoint ...
|
||||
EnvironmentResourceManagerEndpoint EnvironmentProperty = "resourceManagerEndpoint"
|
||||
// EnvironmentActiveDirectoryEndpoint ...
|
||||
EnvironmentActiveDirectoryEndpoint EnvironmentProperty = "activeDirectoryEndpoint"
|
||||
// EnvironmentGalleryEndpoint ...
|
||||
EnvironmentGalleryEndpoint EnvironmentProperty = "galleryEndpoint"
|
||||
// EnvironmentKeyVaultEndpoint ...
|
||||
EnvironmentKeyVaultEndpoint EnvironmentProperty = "keyVaultEndpoint"
|
||||
// EnvironmentGraphEndpoint ...
|
||||
EnvironmentGraphEndpoint EnvironmentProperty = "graphEndpoint"
|
||||
// EnvironmentServiceBusEndpoint ...
|
||||
EnvironmentServiceBusEndpoint EnvironmentProperty = "serviceBusEndpoint"
|
||||
// EnvironmentBatchManagementEndpoint ...
|
||||
EnvironmentBatchManagementEndpoint EnvironmentProperty = "batchManagementEndpoint"
|
||||
// EnvironmentStorageEndpointSuffix ...
|
||||
EnvironmentStorageEndpointSuffix EnvironmentProperty = "storageEndpointSuffix"
|
||||
// EnvironmentSQLDatabaseDNSSuffix ...
|
||||
EnvironmentSQLDatabaseDNSSuffix EnvironmentProperty = "sqlDatabaseDNSSuffix"
|
||||
// EnvironmentTrafficManagerDNSSuffix ...
|
||||
EnvironmentTrafficManagerDNSSuffix EnvironmentProperty = "trafficManagerDNSSuffix"
|
||||
// EnvironmentKeyVaultDNSSuffix ...
|
||||
EnvironmentKeyVaultDNSSuffix EnvironmentProperty = "keyVaultDNSSuffix"
|
||||
// EnvironmentServiceBusEndpointSuffix ...
|
||||
EnvironmentServiceBusEndpointSuffix EnvironmentProperty = "serviceBusEndpointSuffix"
|
||||
// EnvironmentServiceManagementVMDNSSuffix ...
|
||||
EnvironmentServiceManagementVMDNSSuffix EnvironmentProperty = "serviceManagementVMDNSSuffix"
|
||||
// EnvironmentResourceManagerVMDNSSuffix ...
|
||||
EnvironmentResourceManagerVMDNSSuffix EnvironmentProperty = "resourceManagerVMDNSSuffix"
|
||||
// EnvironmentContainerRegistryDNSSuffix ...
|
||||
EnvironmentContainerRegistryDNSSuffix EnvironmentProperty = "containerRegistryDNSSuffix"
|
||||
// EnvironmentTokenAudience ...
|
||||
EnvironmentTokenAudience EnvironmentProperty = "tokenAudience"
|
||||
)
|
||||
|
||||
// OverrideProperty represents property name and value that clients can override
|
||||
type OverrideProperty struct {
|
||||
Key EnvironmentProperty
|
||||
Value string
|
||||
}
|
||||
|
||||
// EnvironmentFromURL loads an Environment from a URL
|
||||
// This function is particularly useful in the Hybrid Cloud model, where one may define their own
|
||||
// endpoints.
|
||||
func EnvironmentFromURL(resourceManagerEndpoint string, properties ...OverrideProperty) (environment Environment, err error) {
|
||||
var metadataEnvProperties environmentMetadataInfo
|
||||
|
||||
if resourceManagerEndpoint == "" {
|
||||
return environment, fmt.Errorf("Metadata resource manager endpoint is empty")
|
||||
}
|
||||
|
||||
if metadataEnvProperties, err = retrieveMetadataEnvironment(resourceManagerEndpoint); err != nil {
|
||||
return environment, err
|
||||
}
|
||||
|
||||
// Give priority to user's override values
|
||||
overrideProperties(&environment, properties)
|
||||
|
||||
if environment.Name == "" {
|
||||
environment.Name = "HybridEnvironment"
|
||||
}
|
||||
stampDNSSuffix := environment.StorageEndpointSuffix
|
||||
if stampDNSSuffix == "" {
|
||||
stampDNSSuffix = strings.TrimSuffix(strings.TrimPrefix(strings.Replace(resourceManagerEndpoint, strings.Split(resourceManagerEndpoint, ".")[0], "", 1), "."), "/")
|
||||
environment.StorageEndpointSuffix = stampDNSSuffix
|
||||
}
|
||||
if environment.KeyVaultDNSSuffix == "" {
|
||||
environment.KeyVaultDNSSuffix = fmt.Sprintf("%s.%s", "vault", stampDNSSuffix)
|
||||
}
|
||||
if environment.KeyVaultEndpoint == "" {
|
||||
environment.KeyVaultEndpoint = fmt.Sprintf("%s%s", "https://", environment.KeyVaultDNSSuffix)
|
||||
}
|
||||
if environment.TokenAudience == "" {
|
||||
environment.TokenAudience = metadataEnvProperties.Authentication.Audiences[0]
|
||||
}
|
||||
if environment.ActiveDirectoryEndpoint == "" {
|
||||
environment.ActiveDirectoryEndpoint = metadataEnvProperties.Authentication.LoginEndpoint
|
||||
}
|
||||
if environment.ResourceManagerEndpoint == "" {
|
||||
environment.ResourceManagerEndpoint = resourceManagerEndpoint
|
||||
}
|
||||
if environment.GalleryEndpoint == "" {
|
||||
environment.GalleryEndpoint = metadataEnvProperties.GalleryEndpoint
|
||||
}
|
||||
if environment.GraphEndpoint == "" {
|
||||
environment.GraphEndpoint = metadataEnvProperties.GraphEndpoint
|
||||
}
|
||||
|
||||
return environment, nil
|
||||
}
|
||||
|
||||
func overrideProperties(environment *Environment, properties []OverrideProperty) {
|
||||
for _, property := range properties {
|
||||
switch property.Key {
|
||||
case EnvironmentName:
|
||||
{
|
||||
environment.Name = property.Value
|
||||
}
|
||||
case EnvironmentManagementPortalURL:
|
||||
{
|
||||
environment.ManagementPortalURL = property.Value
|
||||
}
|
||||
case EnvironmentPublishSettingsURL:
|
||||
{
|
||||
environment.PublishSettingsURL = property.Value
|
||||
}
|
||||
case EnvironmentServiceManagementEndpoint:
|
||||
{
|
||||
environment.ServiceManagementEndpoint = property.Value
|
||||
}
|
||||
case EnvironmentResourceManagerEndpoint:
|
||||
{
|
||||
environment.ResourceManagerEndpoint = property.Value
|
||||
}
|
||||
case EnvironmentActiveDirectoryEndpoint:
|
||||
{
|
||||
environment.ActiveDirectoryEndpoint = property.Value
|
||||
}
|
||||
case EnvironmentGalleryEndpoint:
|
||||
{
|
||||
environment.GalleryEndpoint = property.Value
|
||||
}
|
||||
case EnvironmentKeyVaultEndpoint:
|
||||
{
|
||||
environment.KeyVaultEndpoint = property.Value
|
||||
}
|
||||
case EnvironmentGraphEndpoint:
|
||||
{
|
||||
environment.GraphEndpoint = property.Value
|
||||
}
|
||||
case EnvironmentServiceBusEndpoint:
|
||||
{
|
||||
environment.ServiceBusEndpoint = property.Value
|
||||
}
|
||||
case EnvironmentBatchManagementEndpoint:
|
||||
{
|
||||
environment.BatchManagementEndpoint = property.Value
|
||||
}
|
||||
case EnvironmentStorageEndpointSuffix:
|
||||
{
|
||||
environment.StorageEndpointSuffix = property.Value
|
||||
}
|
||||
case EnvironmentSQLDatabaseDNSSuffix:
|
||||
{
|
||||
environment.SQLDatabaseDNSSuffix = property.Value
|
||||
}
|
||||
case EnvironmentTrafficManagerDNSSuffix:
|
||||
{
|
||||
environment.TrafficManagerDNSSuffix = property.Value
|
||||
}
|
||||
case EnvironmentKeyVaultDNSSuffix:
|
||||
{
|
||||
environment.KeyVaultDNSSuffix = property.Value
|
||||
}
|
||||
case EnvironmentServiceBusEndpointSuffix:
|
||||
{
|
||||
environment.ServiceBusEndpointSuffix = property.Value
|
||||
}
|
||||
case EnvironmentServiceManagementVMDNSSuffix:
|
||||
{
|
||||
environment.ServiceManagementVMDNSSuffix = property.Value
|
||||
}
|
||||
case EnvironmentResourceManagerVMDNSSuffix:
|
||||
{
|
||||
environment.ResourceManagerVMDNSSuffix = property.Value
|
||||
}
|
||||
case EnvironmentContainerRegistryDNSSuffix:
|
||||
{
|
||||
environment.ContainerRegistryDNSSuffix = property.Value
|
||||
}
|
||||
case EnvironmentTokenAudience:
|
||||
{
|
||||
environment.TokenAudience = property.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveMetadataEnvironment(endpoint string) (environment environmentMetadataInfo, err error) {
|
||||
client := autorest.NewClientWithUserAgent("")
|
||||
managementEndpoint := fmt.Sprintf("%s%s", strings.TrimSuffix(endpoint, "/"), "/metadata/endpoints?api-version=1.0")
|
||||
req, _ := http.NewRequest("GET", managementEndpoint, nil)
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
return environment, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
jsonResponse, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return environment, err
|
||||
}
|
||||
err = json.Unmarshal(jsonResponse, &environment)
|
||||
return environment, err
|
||||
}
|
||||
200
vendor/github.com/Azure/go-autorest/autorest/azure/rp.go
generated
vendored
200
vendor/github.com/Azure/go-autorest/autorest/azure/rp.go
generated
vendored
@@ -1,200 +0,0 @@
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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 azure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
)
|
||||
|
||||
// DoRetryWithRegistration tries to register the resource provider in case it is unregistered.
|
||||
// It also handles request retries
|
||||
func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator {
|
||||
return func(s autorest.Sender) autorest.Sender {
|
||||
return autorest.SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
||||
rr := autorest.NewRetriableRequest(r)
|
||||
for currentAttempt := 0; currentAttempt < client.RetryAttempts; currentAttempt++ {
|
||||
err = rr.Prepare()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp, err = autorest.SendWithSender(s, rr.Request(),
|
||||
autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...),
|
||||
)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusConflict || client.SkipResourceProviderRegistration {
|
||||
return resp, err
|
||||
}
|
||||
var re RequestError
|
||||
err = autorest.Respond(
|
||||
resp,
|
||||
autorest.ByUnmarshallingJSON(&re),
|
||||
)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
err = re
|
||||
|
||||
if re.ServiceError != nil && re.ServiceError.Code == "MissingSubscriptionRegistration" {
|
||||
regErr := register(client, r, re)
|
||||
if regErr != nil {
|
||||
return resp, fmt.Errorf("failed auto registering Resource Provider: %s. Original error: %s", regErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getProvider(re RequestError) (string, error) {
|
||||
if re.ServiceError != nil && len(re.ServiceError.Details) > 0 {
|
||||
return re.ServiceError.Details[0]["target"].(string), nil
|
||||
}
|
||||
return "", errors.New("provider was not found in the response")
|
||||
}
|
||||
|
||||
func register(client autorest.Client, originalReq *http.Request, re RequestError) error {
|
||||
subID := getSubscription(originalReq.URL.Path)
|
||||
if subID == "" {
|
||||
return errors.New("missing parameter subscriptionID to register resource provider")
|
||||
}
|
||||
providerName, err := getProvider(re)
|
||||
if err != nil {
|
||||
return fmt.Errorf("missing parameter provider to register resource provider: %s", err)
|
||||
}
|
||||
newURL := url.URL{
|
||||
Scheme: originalReq.URL.Scheme,
|
||||
Host: originalReq.URL.Host,
|
||||
}
|
||||
|
||||
// taken from the resources SDK
|
||||
// with almost identical code, this sections are easier to mantain
|
||||
// It is also not a good idea to import the SDK here
|
||||
// https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L252
|
||||
pathParameters := map[string]interface{}{
|
||||
"resourceProviderNamespace": autorest.Encode("path", providerName),
|
||||
"subscriptionId": autorest.Encode("path", subID),
|
||||
}
|
||||
|
||||
const APIVersion = "2016-09-01"
|
||||
queryParameters := map[string]interface{}{
|
||||
"api-version": APIVersion,
|
||||
}
|
||||
|
||||
preparer := autorest.CreatePreparer(
|
||||
autorest.AsPost(),
|
||||
autorest.WithBaseURL(newURL.String()),
|
||||
autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}/register", pathParameters),
|
||||
autorest.WithQueryParameters(queryParameters),
|
||||
)
|
||||
|
||||
req, err := preparer.Prepare(&http.Request{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(originalReq.Context())
|
||||
|
||||
resp, err := autorest.SendWithSender(client, req,
|
||||
autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
RegistrationState *string `json:"registrationState,omitempty"`
|
||||
}
|
||||
var provider Provider
|
||||
|
||||
err = autorest.Respond(
|
||||
resp,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByUnmarshallingJSON(&provider),
|
||||
autorest.ByClosing(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// poll for registered provisioning state
|
||||
now := time.Now()
|
||||
for err == nil && time.Since(now) < client.PollingDuration {
|
||||
// taken from the resources SDK
|
||||
// https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L45
|
||||
preparer := autorest.CreatePreparer(
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(newURL.String()),
|
||||
autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}", pathParameters),
|
||||
autorest.WithQueryParameters(queryParameters),
|
||||
)
|
||||
req, err = preparer.Prepare(&http.Request{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(originalReq.Context())
|
||||
|
||||
resp, err := autorest.SendWithSender(client, req,
|
||||
autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = autorest.Respond(
|
||||
resp,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByUnmarshallingJSON(&provider),
|
||||
autorest.ByClosing(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if provider.RegistrationState != nil &&
|
||||
*provider.RegistrationState == "Registered" {
|
||||
break
|
||||
}
|
||||
|
||||
delayed := autorest.DelayWithRetryAfter(resp, originalReq.Context().Done())
|
||||
if !delayed && !autorest.DelayForBackoff(client.PollingDelay, 0, originalReq.Context().Done()) {
|
||||
return originalReq.Context().Err()
|
||||
}
|
||||
}
|
||||
if !(time.Since(now) < client.PollingDuration) {
|
||||
return errors.New("polling for resource provider registration has exceeded the polling duration")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func getSubscription(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
for i, v := range parts {
|
||||
if v == "subscriptions" && (i+1) < len(parts) {
|
||||
return parts[i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
256
vendor/github.com/Azure/go-autorest/autorest/client.go
generated
vendored
256
vendor/github.com/Azure/go-autorest/autorest/client.go
generated
vendored
@@ -1,256 +0,0 @@
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultPollingDelay is a reasonable delay between polling requests.
|
||||
DefaultPollingDelay = 60 * time.Second
|
||||
|
||||
// DefaultPollingDuration is a reasonable total polling duration.
|
||||
DefaultPollingDuration = 15 * time.Minute
|
||||
|
||||
// DefaultRetryAttempts is number of attempts for retry status codes (5xx).
|
||||
DefaultRetryAttempts = 3
|
||||
|
||||
// DefaultRetryDuration is the duration to wait between retries.
|
||||
DefaultRetryDuration = 30 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
// StatusCodesForRetry are a defined group of status code for which the client will retry
|
||||
StatusCodesForRetry = []int{
|
||||
http.StatusRequestTimeout, // 408
|
||||
http.StatusTooManyRequests, // 429
|
||||
http.StatusInternalServerError, // 500
|
||||
http.StatusBadGateway, // 502
|
||||
http.StatusServiceUnavailable, // 503
|
||||
http.StatusGatewayTimeout, // 504
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
requestFormat = `HTTP Request Begin ===================================================
|
||||
%s
|
||||
===================================================== HTTP Request End
|
||||
`
|
||||
responseFormat = `HTTP Response Begin ===================================================
|
||||
%s
|
||||
===================================================== HTTP Response End
|
||||
`
|
||||
)
|
||||
|
||||
// Response serves as the base for all responses from generated clients. It provides access to the
|
||||
// last http.Response.
|
||||
type Response struct {
|
||||
*http.Response `json:"-"`
|
||||
}
|
||||
|
||||
// LoggingInspector implements request and response inspectors that log the full request and
|
||||
// response to a supplied log.
|
||||
type LoggingInspector struct {
|
||||
Logger *log.Logger
|
||||
}
|
||||
|
||||
// WithInspection returns a PrepareDecorator that emits the http.Request to the supplied logger. The
|
||||
// body is restored after being emitted.
|
||||
//
|
||||
// Note: Since it reads the entire Body, this decorator should not be used where body streaming is
|
||||
// important. It is best used to trace JSON or similar body values.
|
||||
func (li LoggingInspector) WithInspection() PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
var body, b bytes.Buffer
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
r.Body = ioutil.NopCloser(io.TeeReader(r.Body, &body))
|
||||
if err := r.Write(&b); err != nil {
|
||||
return nil, fmt.Errorf("Failed to write response: %v", err)
|
||||
}
|
||||
|
||||
li.Logger.Printf(requestFormat, b.String())
|
||||
|
||||
r.Body = ioutil.NopCloser(&body)
|
||||
return p.Prepare(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ByInspecting returns a RespondDecorator that emits the http.Response to the supplied logger. The
|
||||
// body is restored after being emitted.
|
||||
//
|
||||
// Note: Since it reads the entire Body, this decorator should not be used where body streaming is
|
||||
// important. It is best used to trace JSON or similar body values.
|
||||
func (li LoggingInspector) ByInspecting() RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
var body, b bytes.Buffer
|
||||
defer resp.Body.Close()
|
||||
resp.Body = ioutil.NopCloser(io.TeeReader(resp.Body, &body))
|
||||
if err := resp.Write(&b); err != nil {
|
||||
return fmt.Errorf("Failed to write response: %v", err)
|
||||
}
|
||||
|
||||
li.Logger.Printf(responseFormat, b.String())
|
||||
|
||||
resp.Body = ioutil.NopCloser(&body)
|
||||
return r.Respond(resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Client is the base for autorest generated clients. It provides default, "do nothing"
|
||||
// implementations of an Authorizer, RequestInspector, and ResponseInspector. It also returns the
|
||||
// standard, undecorated http.Client as a default Sender.
|
||||
//
|
||||
// Generated clients should also use Error (see NewError and NewErrorWithError) for errors and
|
||||
// return responses that compose with Response.
|
||||
//
|
||||
// Most customization of generated clients is best achieved by supplying a custom Authorizer, custom
|
||||
// RequestInspector, and / or custom ResponseInspector. Users may log requests, implement circuit
|
||||
// breakers (see https://msdn.microsoft.com/en-us/library/dn589784.aspx) or otherwise influence
|
||||
// sending the request by providing a decorated Sender.
|
||||
type Client struct {
|
||||
Authorizer Authorizer
|
||||
Sender Sender
|
||||
RequestInspector PrepareDecorator
|
||||
ResponseInspector RespondDecorator
|
||||
|
||||
// PollingDelay sets the polling frequency used in absence of a Retry-After HTTP header
|
||||
PollingDelay time.Duration
|
||||
|
||||
// PollingDuration sets the maximum polling time after which an error is returned.
|
||||
PollingDuration time.Duration
|
||||
|
||||
// RetryAttempts sets the default number of retry attempts for client.
|
||||
RetryAttempts int
|
||||
|
||||
// RetryDuration sets the delay duration for retries.
|
||||
RetryDuration time.Duration
|
||||
|
||||
// UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent
|
||||
// through the Do method.
|
||||
UserAgent string
|
||||
|
||||
Jar http.CookieJar
|
||||
|
||||
// Set to true to skip attempted registration of resource providers (false by default).
|
||||
SkipResourceProviderRegistration bool
|
||||
}
|
||||
|
||||
// NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed
|
||||
// string.
|
||||
func NewClientWithUserAgent(ua string) Client {
|
||||
c := Client{
|
||||
PollingDelay: DefaultPollingDelay,
|
||||
PollingDuration: DefaultPollingDuration,
|
||||
RetryAttempts: DefaultRetryAttempts,
|
||||
RetryDuration: DefaultRetryDuration,
|
||||
UserAgent: version.UserAgent(),
|
||||
}
|
||||
c.Sender = c.sender()
|
||||
c.AddToUserAgent(ua)
|
||||
return c
|
||||
}
|
||||
|
||||
// AddToUserAgent adds an extension to the current user agent
|
||||
func (c *Client) AddToUserAgent(extension string) error {
|
||||
if extension != "" {
|
||||
c.UserAgent = fmt.Sprintf("%s %s", c.UserAgent, extension)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent)
|
||||
}
|
||||
|
||||
// Do implements the Sender interface by invoking the active Sender after applying authorization.
|
||||
// If Sender is not set, it uses a new instance of http.Client. In both cases it will, if UserAgent
|
||||
// is set, apply set the User-Agent header.
|
||||
func (c Client) Do(r *http.Request) (*http.Response, error) {
|
||||
if r.UserAgent() == "" {
|
||||
r, _ = Prepare(r,
|
||||
WithUserAgent(c.UserAgent))
|
||||
}
|
||||
// NOTE: c.WithInspection() must be last in the list so that it can inspect all preceding operations
|
||||
r, err := Prepare(r,
|
||||
c.WithAuthorization(),
|
||||
c.WithInspection())
|
||||
if err != nil {
|
||||
var resp *http.Response
|
||||
if detErr, ok := err.(DetailedError); ok {
|
||||
// if the authorization failed (e.g. invalid credentials) there will
|
||||
// be a response associated with the error, be sure to return it.
|
||||
resp = detErr.Response
|
||||
}
|
||||
return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
|
||||
}
|
||||
|
||||
resp, err := SendWithSender(c.sender(), r)
|
||||
Respond(resp, c.ByInspecting())
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// sender returns the Sender to which to send requests.
|
||||
func (c Client) sender() Sender {
|
||||
if c.Sender == nil {
|
||||
j, _ := cookiejar.New(nil)
|
||||
return &http.Client{Jar: j}
|
||||
}
|
||||
return c.Sender
|
||||
}
|
||||
|
||||
// WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator
|
||||
// from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer.
|
||||
func (c Client) WithAuthorization() PrepareDecorator {
|
||||
return c.authorizer().WithAuthorization()
|
||||
}
|
||||
|
||||
// authorizer returns the Authorizer to use.
|
||||
func (c Client) authorizer() Authorizer {
|
||||
if c.Authorizer == nil {
|
||||
return NullAuthorizer{}
|
||||
}
|
||||
return c.Authorizer
|
||||
}
|
||||
|
||||
// WithInspection is a convenience method that passes the request to the supplied RequestInspector,
|
||||
// if present, or returns the WithNothing PrepareDecorator otherwise.
|
||||
func (c Client) WithInspection() PrepareDecorator {
|
||||
if c.RequestInspector == nil {
|
||||
return WithNothing()
|
||||
}
|
||||
return c.RequestInspector
|
||||
}
|
||||
|
||||
// ByInspecting is a convenience method that passes the response to the supplied ResponseInspector,
|
||||
// if present, or returns the ByIgnoring RespondDecorator otherwise.
|
||||
func (c Client) ByInspecting() RespondDecorator {
|
||||
if c.ResponseInspector == nil {
|
||||
return ByIgnoring()
|
||||
}
|
||||
return c.ResponseInspector
|
||||
}
|
||||
96
vendor/github.com/Azure/go-autorest/autorest/date/date.go
generated
vendored
96
vendor/github.com/Azure/go-autorest/autorest/date/date.go
generated
vendored
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
Package date provides time.Time derivatives that conform to the Swagger.io (https://swagger.io/)
|
||||
defined date formats: Date and DateTime. Both types may, in most cases, be used in lieu of
|
||||
time.Time types. And both convert to time.Time through a ToTime method.
|
||||
*/
|
||||
package date
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
fullDate = "2006-01-02"
|
||||
fullDateJSON = `"2006-01-02"`
|
||||
dateFormat = "%04d-%02d-%02d"
|
||||
jsonFormat = `"%04d-%02d-%02d"`
|
||||
)
|
||||
|
||||
// Date defines a type similar to time.Time but assumes a layout of RFC3339 full-date (i.e.,
|
||||
// 2006-01-02).
|
||||
type Date struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// ParseDate create a new Date from the passed string.
|
||||
func ParseDate(date string) (d Date, err error) {
|
||||
return parseDate(date, fullDate)
|
||||
}
|
||||
|
||||
func parseDate(date string, format string) (Date, error) {
|
||||
d, err := time.Parse(format, date)
|
||||
return Date{Time: d}, err
|
||||
}
|
||||
|
||||
// MarshalBinary preserves the Date as a byte array conforming to RFC3339 full-date (i.e.,
|
||||
// 2006-01-02).
|
||||
func (d Date) MarshalBinary() ([]byte, error) {
|
||||
return d.MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalBinary reconstitutes a Date saved as a byte array conforming to RFC3339 full-date (i.e.,
|
||||
// 2006-01-02).
|
||||
func (d *Date) UnmarshalBinary(data []byte) error {
|
||||
return d.UnmarshalText(data)
|
||||
}
|
||||
|
||||
// MarshalJSON preserves the Date as a JSON string conforming to RFC3339 full-date (i.e.,
|
||||
// 2006-01-02).
|
||||
func (d Date) MarshalJSON() (json []byte, err error) {
|
||||
return []byte(fmt.Sprintf(jsonFormat, d.Year(), d.Month(), d.Day())), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON reconstitutes the Date from a JSON string conforming to RFC3339 full-date (i.e.,
|
||||
// 2006-01-02).
|
||||
func (d *Date) UnmarshalJSON(data []byte) (err error) {
|
||||
d.Time, err = time.Parse(fullDateJSON, string(data))
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalText preserves the Date as a byte array conforming to RFC3339 full-date (i.e.,
|
||||
// 2006-01-02).
|
||||
func (d Date) MarshalText() (text []byte, err error) {
|
||||
return []byte(fmt.Sprintf(dateFormat, d.Year(), d.Month(), d.Day())), nil
|
||||
}
|
||||
|
||||
// UnmarshalText reconstitutes a Date saved as a byte array conforming to RFC3339 full-date (i.e.,
|
||||
// 2006-01-02).
|
||||
func (d *Date) UnmarshalText(data []byte) (err error) {
|
||||
d.Time, err = time.Parse(fullDate, string(data))
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns the Date formatted as an RFC3339 full-date string (i.e., 2006-01-02).
|
||||
func (d Date) String() string {
|
||||
return fmt.Sprintf(dateFormat, d.Year(), d.Month(), d.Day())
|
||||
}
|
||||
|
||||
// ToTime returns a Date as a time.Time
|
||||
func (d Date) ToTime() time.Time {
|
||||
return d.Time
|
||||
}
|
||||
103
vendor/github.com/Azure/go-autorest/autorest/date/time.go
generated
vendored
103
vendor/github.com/Azure/go-autorest/autorest/date/time.go
generated
vendored
@@ -1,103 +0,0 @@
|
||||
package date
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Azure reports time in UTC but it doesn't include the 'Z' time zone suffix in some cases.
|
||||
const (
|
||||
azureUtcFormatJSON = `"2006-01-02T15:04:05.999999999"`
|
||||
azureUtcFormat = "2006-01-02T15:04:05.999999999"
|
||||
rfc3339JSON = `"` + time.RFC3339Nano + `"`
|
||||
rfc3339 = time.RFC3339Nano
|
||||
tzOffsetRegex = `(Z|z|\+|-)(\d+:\d+)*"*$`
|
||||
)
|
||||
|
||||
// Time defines a type similar to time.Time but assumes a layout of RFC3339 date-time (i.e.,
|
||||
// 2006-01-02T15:04:05Z).
|
||||
type Time struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// MarshalBinary preserves the Time as a byte array conforming to RFC3339 date-time (i.e.,
|
||||
// 2006-01-02T15:04:05Z).
|
||||
func (t Time) MarshalBinary() ([]byte, error) {
|
||||
return t.Time.MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalBinary reconstitutes a Time saved as a byte array conforming to RFC3339 date-time
|
||||
// (i.e., 2006-01-02T15:04:05Z).
|
||||
func (t *Time) UnmarshalBinary(data []byte) error {
|
||||
return t.UnmarshalText(data)
|
||||
}
|
||||
|
||||
// MarshalJSON preserves the Time as a JSON string conforming to RFC3339 date-time (i.e.,
|
||||
// 2006-01-02T15:04:05Z).
|
||||
func (t Time) MarshalJSON() (json []byte, err error) {
|
||||
return t.Time.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON reconstitutes the Time from a JSON string conforming to RFC3339 date-time
|
||||
// (i.e., 2006-01-02T15:04:05Z).
|
||||
func (t *Time) UnmarshalJSON(data []byte) (err error) {
|
||||
timeFormat := azureUtcFormatJSON
|
||||
match, err := regexp.Match(tzOffsetRegex, data)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if match {
|
||||
timeFormat = rfc3339JSON
|
||||
}
|
||||
t.Time, err = ParseTime(timeFormat, string(data))
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalText preserves the Time as a byte array conforming to RFC3339 date-time (i.e.,
|
||||
// 2006-01-02T15:04:05Z).
|
||||
func (t Time) MarshalText() (text []byte, err error) {
|
||||
return t.Time.MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalText reconstitutes a Time saved as a byte array conforming to RFC3339 date-time
|
||||
// (i.e., 2006-01-02T15:04:05Z).
|
||||
func (t *Time) UnmarshalText(data []byte) (err error) {
|
||||
timeFormat := azureUtcFormat
|
||||
match, err := regexp.Match(tzOffsetRegex, data)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if match {
|
||||
timeFormat = rfc3339
|
||||
}
|
||||
t.Time, err = ParseTime(timeFormat, string(data))
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns the Time formatted as an RFC3339 date-time string (i.e.,
|
||||
// 2006-01-02T15:04:05Z).
|
||||
func (t Time) String() string {
|
||||
// Note: time.Time.String does not return an RFC3339 compliant string, time.Time.MarshalText does.
|
||||
b, err := t.MarshalText()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// ToTime returns a Time as a time.Time
|
||||
func (t Time) ToTime() time.Time {
|
||||
return t.Time
|
||||
}
|
||||
100
vendor/github.com/Azure/go-autorest/autorest/date/timerfc1123.go
generated
vendored
100
vendor/github.com/Azure/go-autorest/autorest/date/timerfc1123.go
generated
vendored
@@ -1,100 +0,0 @@
|
||||
package date
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
rfc1123JSON = `"` + time.RFC1123 + `"`
|
||||
rfc1123 = time.RFC1123
|
||||
)
|
||||
|
||||
// TimeRFC1123 defines a type similar to time.Time but assumes a layout of RFC1123 date-time (i.e.,
|
||||
// Mon, 02 Jan 2006 15:04:05 MST).
|
||||
type TimeRFC1123 struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// UnmarshalJSON reconstitutes the Time from a JSON string conforming to RFC1123 date-time
|
||||
// (i.e., Mon, 02 Jan 2006 15:04:05 MST).
|
||||
func (t *TimeRFC1123) UnmarshalJSON(data []byte) (err error) {
|
||||
t.Time, err = ParseTime(rfc1123JSON, string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON preserves the Time as a JSON string conforming to RFC1123 date-time (i.e.,
|
||||
// Mon, 02 Jan 2006 15:04:05 MST).
|
||||
func (t TimeRFC1123) MarshalJSON() ([]byte, error) {
|
||||
if y := t.Year(); y < 0 || y >= 10000 {
|
||||
return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
|
||||
}
|
||||
b := []byte(t.Format(rfc1123JSON))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// MarshalText preserves the Time as a byte array conforming to RFC1123 date-time (i.e.,
|
||||
// Mon, 02 Jan 2006 15:04:05 MST).
|
||||
func (t TimeRFC1123) MarshalText() ([]byte, error) {
|
||||
if y := t.Year(); y < 0 || y >= 10000 {
|
||||
return nil, errors.New("Time.MarshalText: year outside of range [0,9999]")
|
||||
}
|
||||
|
||||
b := []byte(t.Format(rfc1123))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// UnmarshalText reconstitutes a Time saved as a byte array conforming to RFC1123 date-time
|
||||
// (i.e., Mon, 02 Jan 2006 15:04:05 MST).
|
||||
func (t *TimeRFC1123) UnmarshalText(data []byte) (err error) {
|
||||
t.Time, err = ParseTime(rfc1123, string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary preserves the Time as a byte array conforming to RFC1123 date-time (i.e.,
|
||||
// Mon, 02 Jan 2006 15:04:05 MST).
|
||||
func (t TimeRFC1123) MarshalBinary() ([]byte, error) {
|
||||
return t.MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalBinary reconstitutes a Time saved as a byte array conforming to RFC1123 date-time
|
||||
// (i.e., Mon, 02 Jan 2006 15:04:05 MST).
|
||||
func (t *TimeRFC1123) UnmarshalBinary(data []byte) error {
|
||||
return t.UnmarshalText(data)
|
||||
}
|
||||
|
||||
// ToTime returns a Time as a time.Time
|
||||
func (t TimeRFC1123) ToTime() time.Time {
|
||||
return t.Time
|
||||
}
|
||||
|
||||
// String returns the Time formatted as an RFC1123 date-time string (i.e.,
|
||||
// Mon, 02 Jan 2006 15:04:05 MST).
|
||||
func (t TimeRFC1123) String() string {
|
||||
// Note: time.Time.String does not return an RFC1123 compliant string, time.Time.MarshalText does.
|
||||
b, err := t.MarshalText()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
123
vendor/github.com/Azure/go-autorest/autorest/date/unixtime.go
generated
vendored
123
vendor/github.com/Azure/go-autorest/autorest/date/unixtime.go
generated
vendored
@@ -1,123 +0,0 @@
|
||||
package date
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// unixEpoch is the moment in time that should be treated as timestamp 0.
|
||||
var unixEpoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// UnixTime marshals and unmarshals a time that is represented as the number
|
||||
// of seconds (ignoring skip-seconds) since the Unix Epoch.
|
||||
type UnixTime time.Time
|
||||
|
||||
// Duration returns the time as a Duration since the UnixEpoch.
|
||||
func (t UnixTime) Duration() time.Duration {
|
||||
return time.Time(t).Sub(unixEpoch)
|
||||
}
|
||||
|
||||
// NewUnixTimeFromSeconds creates a UnixTime as a number of seconds from the UnixEpoch.
|
||||
func NewUnixTimeFromSeconds(seconds float64) UnixTime {
|
||||
return NewUnixTimeFromDuration(time.Duration(seconds * float64(time.Second)))
|
||||
}
|
||||
|
||||
// NewUnixTimeFromNanoseconds creates a UnixTime as a number of nanoseconds from the UnixEpoch.
|
||||
func NewUnixTimeFromNanoseconds(nanoseconds int64) UnixTime {
|
||||
return NewUnixTimeFromDuration(time.Duration(nanoseconds))
|
||||
}
|
||||
|
||||
// NewUnixTimeFromDuration creates a UnixTime as a duration of time since the UnixEpoch.
|
||||
func NewUnixTimeFromDuration(dur time.Duration) UnixTime {
|
||||
return UnixTime(unixEpoch.Add(dur))
|
||||
}
|
||||
|
||||
// UnixEpoch retreives the moment considered the Unix Epoch. I.e. The time represented by '0'
|
||||
func UnixEpoch() time.Time {
|
||||
return unixEpoch
|
||||
}
|
||||
|
||||
// MarshalJSON preserves the UnixTime as a JSON number conforming to Unix Timestamp requirements.
|
||||
// (i.e. the number of seconds since midnight January 1st, 1970 not considering leap seconds.)
|
||||
func (t UnixTime) MarshalJSON() ([]byte, error) {
|
||||
buffer := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(buffer)
|
||||
err := enc.Encode(float64(time.Time(t).UnixNano()) / 1e9)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON reconstitures a UnixTime saved as a JSON number of the number of seconds since
|
||||
// midnight January 1st, 1970.
|
||||
func (t *UnixTime) UnmarshalJSON(text []byte) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(text))
|
||||
|
||||
var secondsSinceEpoch float64
|
||||
if err := dec.Decode(&secondsSinceEpoch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = NewUnixTimeFromSeconds(secondsSinceEpoch)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText stores the number of seconds since the Unix Epoch as a textual floating point number.
|
||||
func (t UnixTime) MarshalText() ([]byte, error) {
|
||||
cast := time.Time(t)
|
||||
return cast.MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalText populates a UnixTime with a value stored textually as a floating point number of seconds since the Unix Epoch.
|
||||
func (t *UnixTime) UnmarshalText(raw []byte) error {
|
||||
var unmarshaled time.Time
|
||||
|
||||
if err := unmarshaled.UnmarshalText(raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = UnixTime(unmarshaled)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary converts a UnixTime into a binary.LittleEndian float64 of nanoseconds since the epoch.
|
||||
func (t UnixTime) MarshalBinary() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
payload := int64(t.Duration())
|
||||
|
||||
if err := binary.Write(buf, binary.LittleEndian, &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary converts a from a binary.LittleEndian float64 of nanoseconds since the epoch into a UnixTime.
|
||||
func (t *UnixTime) UnmarshalBinary(raw []byte) error {
|
||||
var nanosecondsSinceEpoch int64
|
||||
|
||||
if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, &nanosecondsSinceEpoch); err != nil {
|
||||
return err
|
||||
}
|
||||
*t = NewUnixTimeFromNanoseconds(nanosecondsSinceEpoch)
|
||||
return nil
|
||||
}
|
||||
25
vendor/github.com/Azure/go-autorest/autorest/date/utility.go
generated
vendored
25
vendor/github.com/Azure/go-autorest/autorest/date/utility.go
generated
vendored
@@ -1,25 +0,0 @@
|
||||
package date
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ParseTime to parse Time string to specified format.
|
||||
func ParseTime(format string, t string) (d time.Time, err error) {
|
||||
return time.Parse(format, strings.ToUpper(t))
|
||||
}
|
||||
98
vendor/github.com/Azure/go-autorest/autorest/error.go
generated
vendored
98
vendor/github.com/Azure/go-autorest/autorest/error.go
generated
vendored
@@ -1,98 +0,0 @@
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
// UndefinedStatusCode is used when HTTP status code is not available for an error.
|
||||
UndefinedStatusCode = 0
|
||||
)
|
||||
|
||||
// DetailedError encloses a error with details of the package, method, and associated HTTP
|
||||
// status code (if any).
|
||||
type DetailedError struct {
|
||||
Original error
|
||||
|
||||
// PackageType is the package type of the object emitting the error. For types, the value
|
||||
// matches that produced the the '%T' format specifier of the fmt package. For other elements,
|
||||
// such as functions, it is just the package name (e.g., "autorest").
|
||||
PackageType string
|
||||
|
||||
// Method is the name of the method raising the error.
|
||||
Method string
|
||||
|
||||
// StatusCode is the HTTP Response StatusCode (if non-zero) that led to the error.
|
||||
StatusCode interface{}
|
||||
|
||||
// Message is the error message.
|
||||
Message string
|
||||
|
||||
// Service Error is the response body of failed API in bytes
|
||||
ServiceError []byte
|
||||
|
||||
// Response is the response object that was returned during failure if applicable.
|
||||
Response *http.Response
|
||||
}
|
||||
|
||||
// NewError creates a new Error conforming object from the passed packageType, method, and
|
||||
// message. message is treated as a format string to which the optional args apply.
|
||||
func NewError(packageType string, method string, message string, args ...interface{}) DetailedError {
|
||||
return NewErrorWithError(nil, packageType, method, nil, message, args...)
|
||||
}
|
||||
|
||||
// NewErrorWithResponse creates a new Error conforming object from the passed
|
||||
// packageType, method, statusCode of the given resp (UndefinedStatusCode if
|
||||
// resp is nil), and message. message is treated as a format string to which the
|
||||
// optional args apply.
|
||||
func NewErrorWithResponse(packageType string, method string, resp *http.Response, message string, args ...interface{}) DetailedError {
|
||||
return NewErrorWithError(nil, packageType, method, resp, message, args...)
|
||||
}
|
||||
|
||||
// NewErrorWithError creates a new Error conforming object from the
|
||||
// passed packageType, method, statusCode of the given resp (UndefinedStatusCode
|
||||
// if resp is nil), message, and original error. message is treated as a format
|
||||
// string to which the optional args apply.
|
||||
func NewErrorWithError(original error, packageType string, method string, resp *http.Response, message string, args ...interface{}) DetailedError {
|
||||
if v, ok := original.(DetailedError); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
statusCode := UndefinedStatusCode
|
||||
if resp != nil {
|
||||
statusCode = resp.StatusCode
|
||||
}
|
||||
|
||||
return DetailedError{
|
||||
Original: original,
|
||||
PackageType: packageType,
|
||||
Method: method,
|
||||
StatusCode: statusCode,
|
||||
Message: fmt.Sprintf(message, args...),
|
||||
Response: resp,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns a formatted containing all available details (i.e., PackageType, Method,
|
||||
// StatusCode, Message, and original error (if any)).
|
||||
func (e DetailedError) Error() string {
|
||||
if e.Original == nil {
|
||||
return fmt.Sprintf("%s#%s: %s: StatusCode=%d", e.PackageType, e.Method, e.Message, e.StatusCode)
|
||||
}
|
||||
return fmt.Sprintf("%s#%s: %s: StatusCode=%d -- Original Error: %v", e.PackageType, e.Method, e.Message, e.StatusCode, e.Original)
|
||||
}
|
||||
480
vendor/github.com/Azure/go-autorest/autorest/preparer.go
generated
vendored
480
vendor/github.com/Azure/go-autorest/autorest/preparer.go
generated
vendored
@@ -1,480 +0,0 @@
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
mimeTypeJSON = "application/json"
|
||||
mimeTypeOctetStream = "application/octet-stream"
|
||||
mimeTypeFormPost = "application/x-www-form-urlencoded"
|
||||
|
||||
headerAuthorization = "Authorization"
|
||||
headerContentType = "Content-Type"
|
||||
headerUserAgent = "User-Agent"
|
||||
)
|
||||
|
||||
// Preparer is the interface that wraps the Prepare method.
|
||||
//
|
||||
// Prepare accepts and possibly modifies an http.Request (e.g., adding Headers). Implementations
|
||||
// must ensure to not share or hold per-invocation state since Preparers may be shared and re-used.
|
||||
type Preparer interface {
|
||||
Prepare(*http.Request) (*http.Request, error)
|
||||
}
|
||||
|
||||
// PreparerFunc is a method that implements the Preparer interface.
|
||||
type PreparerFunc func(*http.Request) (*http.Request, error)
|
||||
|
||||
// Prepare implements the Preparer interface on PreparerFunc.
|
||||
func (pf PreparerFunc) Prepare(r *http.Request) (*http.Request, error) {
|
||||
return pf(r)
|
||||
}
|
||||
|
||||
// PrepareDecorator takes and possibly decorates, by wrapping, a Preparer. Decorators may affect the
|
||||
// http.Request and pass it along or, first, pass the http.Request along then affect the result.
|
||||
type PrepareDecorator func(Preparer) Preparer
|
||||
|
||||
// CreatePreparer creates, decorates, and returns a Preparer.
|
||||
// Without decorators, the returned Preparer returns the passed http.Request unmodified.
|
||||
// Preparers are safe to share and re-use.
|
||||
func CreatePreparer(decorators ...PrepareDecorator) Preparer {
|
||||
return DecoratePreparer(
|
||||
Preparer(PreparerFunc(func(r *http.Request) (*http.Request, error) { return r, nil })),
|
||||
decorators...)
|
||||
}
|
||||
|
||||
// DecoratePreparer accepts a Preparer and a, possibly empty, set of PrepareDecorators, which it
|
||||
// applies to the Preparer. Decorators are applied in the order received, but their affect upon the
|
||||
// request depends on whether they are a pre-decorator (change the http.Request and then pass it
|
||||
// along) or a post-decorator (pass the http.Request along and alter it on return).
|
||||
func DecoratePreparer(p Preparer, decorators ...PrepareDecorator) Preparer {
|
||||
for _, decorate := range decorators {
|
||||
p = decorate(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Prepare accepts an http.Request and a, possibly empty, set of PrepareDecorators.
|
||||
// It creates a Preparer from the decorators which it then applies to the passed http.Request.
|
||||
func Prepare(r *http.Request, decorators ...PrepareDecorator) (*http.Request, error) {
|
||||
if r == nil {
|
||||
return nil, NewError("autorest", "Prepare", "Invoked without an http.Request")
|
||||
}
|
||||
return CreatePreparer(decorators...).Prepare(r)
|
||||
}
|
||||
|
||||
// WithNothing returns a "do nothing" PrepareDecorator that makes no changes to the passed
|
||||
// http.Request.
|
||||
func WithNothing() PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
return p.Prepare(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeader returns a PrepareDecorator that sets the specified HTTP header of the http.Request to
|
||||
// the passed value. It canonicalizes the passed header name (via http.CanonicalHeaderKey) before
|
||||
// adding the header.
|
||||
func WithHeader(header string, value string) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
if r.Header == nil {
|
||||
r.Header = make(http.Header)
|
||||
}
|
||||
r.Header.Set(http.CanonicalHeaderKey(header), value)
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeaders returns a PrepareDecorator that sets the specified HTTP headers of the http.Request to
|
||||
// the passed value. It canonicalizes the passed headers name (via http.CanonicalHeaderKey) before
|
||||
// adding them.
|
||||
func WithHeaders(headers map[string]interface{}) PrepareDecorator {
|
||||
h := ensureValueStrings(headers)
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
if r.Header == nil {
|
||||
r.Header = make(http.Header)
|
||||
}
|
||||
|
||||
for name, value := range h {
|
||||
r.Header.Set(http.CanonicalHeaderKey(name), value)
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithBearerAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
|
||||
// value is "Bearer " followed by the supplied token.
|
||||
func WithBearerAuthorization(token string) PrepareDecorator {
|
||||
return WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", token))
|
||||
}
|
||||
|
||||
// AsContentType returns a PrepareDecorator that adds an HTTP Content-Type header whose value
|
||||
// is the passed contentType.
|
||||
func AsContentType(contentType string) PrepareDecorator {
|
||||
return WithHeader(headerContentType, contentType)
|
||||
}
|
||||
|
||||
// WithUserAgent returns a PrepareDecorator that adds an HTTP User-Agent header whose value is the
|
||||
// passed string.
|
||||
func WithUserAgent(ua string) PrepareDecorator {
|
||||
return WithHeader(headerUserAgent, ua)
|
||||
}
|
||||
|
||||
// AsFormURLEncoded returns a PrepareDecorator that adds an HTTP Content-Type header whose value is
|
||||
// "application/x-www-form-urlencoded".
|
||||
func AsFormURLEncoded() PrepareDecorator {
|
||||
return AsContentType(mimeTypeFormPost)
|
||||
}
|
||||
|
||||
// AsJSON returns a PrepareDecorator that adds an HTTP Content-Type header whose value is
|
||||
// "application/json".
|
||||
func AsJSON() PrepareDecorator {
|
||||
return AsContentType(mimeTypeJSON)
|
||||
}
|
||||
|
||||
// AsOctetStream returns a PrepareDecorator that adds the "application/octet-stream" Content-Type header.
|
||||
func AsOctetStream() PrepareDecorator {
|
||||
return AsContentType(mimeTypeOctetStream)
|
||||
}
|
||||
|
||||
// WithMethod returns a PrepareDecorator that sets the HTTP method of the passed request. The
|
||||
// decorator does not validate that the passed method string is a known HTTP method.
|
||||
func WithMethod(method string) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r.Method = method
|
||||
return p.Prepare(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AsDelete returns a PrepareDecorator that sets the HTTP method to DELETE.
|
||||
func AsDelete() PrepareDecorator { return WithMethod("DELETE") }
|
||||
|
||||
// AsGet returns a PrepareDecorator that sets the HTTP method to GET.
|
||||
func AsGet() PrepareDecorator { return WithMethod("GET") }
|
||||
|
||||
// AsHead returns a PrepareDecorator that sets the HTTP method to HEAD.
|
||||
func AsHead() PrepareDecorator { return WithMethod("HEAD") }
|
||||
|
||||
// AsOptions returns a PrepareDecorator that sets the HTTP method to OPTIONS.
|
||||
func AsOptions() PrepareDecorator { return WithMethod("OPTIONS") }
|
||||
|
||||
// AsPatch returns a PrepareDecorator that sets the HTTP method to PATCH.
|
||||
func AsPatch() PrepareDecorator { return WithMethod("PATCH") }
|
||||
|
||||
// AsPost returns a PrepareDecorator that sets the HTTP method to POST.
|
||||
func AsPost() PrepareDecorator { return WithMethod("POST") }
|
||||
|
||||
// AsPut returns a PrepareDecorator that sets the HTTP method to PUT.
|
||||
func AsPut() PrepareDecorator { return WithMethod("PUT") }
|
||||
|
||||
// WithBaseURL returns a PrepareDecorator that populates the http.Request with a url.URL constructed
|
||||
// from the supplied baseUrl.
|
||||
func WithBaseURL(baseURL string) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
var u *url.URL
|
||||
if u, err = url.Parse(baseURL); err != nil {
|
||||
return r, err
|
||||
}
|
||||
if u.Scheme == "" {
|
||||
err = fmt.Errorf("autorest: No scheme detected in URL %s", baseURL)
|
||||
}
|
||||
if err == nil {
|
||||
r.URL = u
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithCustomBaseURL returns a PrepareDecorator that replaces brace-enclosed keys within the
|
||||
// request base URL (i.e., http.Request.URL) with the corresponding values from the passed map.
|
||||
func WithCustomBaseURL(baseURL string, urlParameters map[string]interface{}) PrepareDecorator {
|
||||
parameters := ensureValueStrings(urlParameters)
|
||||
for key, value := range parameters {
|
||||
baseURL = strings.Replace(baseURL, "{"+key+"}", value, -1)
|
||||
}
|
||||
return WithBaseURL(baseURL)
|
||||
}
|
||||
|
||||
// WithFormData returns a PrepareDecoratore that "URL encodes" (e.g., bar=baz&foo=quux) into the
|
||||
// http.Request body.
|
||||
func WithFormData(v url.Values) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
s := v.Encode()
|
||||
|
||||
if r.Header == nil {
|
||||
r.Header = make(http.Header)
|
||||
}
|
||||
r.Header.Set(http.CanonicalHeaderKey(headerContentType), mimeTypeFormPost)
|
||||
r.ContentLength = int64(len(s))
|
||||
r.Body = ioutil.NopCloser(strings.NewReader(s))
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMultiPartFormData returns a PrepareDecoratore that "URL encodes" (e.g., bar=baz&foo=quux) form parameters
|
||||
// into the http.Request body.
|
||||
func WithMultiPartFormData(formDataParameters map[string]interface{}) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
for key, value := range formDataParameters {
|
||||
if rc, ok := value.(io.ReadCloser); ok {
|
||||
var fd io.Writer
|
||||
if fd, err = writer.CreateFormFile(key, key); err != nil {
|
||||
return r, err
|
||||
}
|
||||
if _, err = io.Copy(fd, rc); err != nil {
|
||||
return r, err
|
||||
}
|
||||
} else {
|
||||
if err = writer.WriteField(key, ensureValueString(value)); err != nil {
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = writer.Close(); err != nil {
|
||||
return r, err
|
||||
}
|
||||
if r.Header == nil {
|
||||
r.Header = make(http.Header)
|
||||
}
|
||||
r.Header.Set(http.CanonicalHeaderKey(headerContentType), writer.FormDataContentType())
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||
r.ContentLength = int64(body.Len())
|
||||
return r, err
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithFile returns a PrepareDecorator that sends file in request body.
|
||||
func WithFile(f io.ReadCloser) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
b, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(b))
|
||||
r.ContentLength = int64(len(b))
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithBool returns a PrepareDecorator that encodes the passed bool into the body of the request
|
||||
// and sets the Content-Length header.
|
||||
func WithBool(v bool) PrepareDecorator {
|
||||
return WithString(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
// WithFloat32 returns a PrepareDecorator that encodes the passed float32 into the body of the
|
||||
// request and sets the Content-Length header.
|
||||
func WithFloat32(v float32) PrepareDecorator {
|
||||
return WithString(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
// WithFloat64 returns a PrepareDecorator that encodes the passed float64 into the body of the
|
||||
// request and sets the Content-Length header.
|
||||
func WithFloat64(v float64) PrepareDecorator {
|
||||
return WithString(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
// WithInt32 returns a PrepareDecorator that encodes the passed int32 into the body of the request
|
||||
// and sets the Content-Length header.
|
||||
func WithInt32(v int32) PrepareDecorator {
|
||||
return WithString(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
// WithInt64 returns a PrepareDecorator that encodes the passed int64 into the body of the request
|
||||
// and sets the Content-Length header.
|
||||
func WithInt64(v int64) PrepareDecorator {
|
||||
return WithString(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
// WithString returns a PrepareDecorator that encodes the passed string into the body of the request
|
||||
// and sets the Content-Length header.
|
||||
func WithString(v string) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
r.ContentLength = int64(len(v))
|
||||
r.Body = ioutil.NopCloser(strings.NewReader(v))
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithJSON returns a PrepareDecorator that encodes the data passed as JSON into the body of the
|
||||
// request and sets the Content-Length header.
|
||||
func WithJSON(v interface{}) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
b, err := json.Marshal(v)
|
||||
if err == nil {
|
||||
r.ContentLength = int64(len(b))
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(b))
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithPath returns a PrepareDecorator that adds the supplied path to the request URL. If the path
|
||||
// is absolute (that is, it begins with a "/"), it replaces the existing path.
|
||||
func WithPath(path string) PrepareDecorator {
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
if r.URL == nil {
|
||||
return r, NewError("autorest", "WithPath", "Invoked with a nil URL")
|
||||
}
|
||||
if r.URL, err = parseURL(r.URL, path); err != nil {
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithEscapedPathParameters returns a PrepareDecorator that replaces brace-enclosed keys within the
|
||||
// request path (i.e., http.Request.URL.Path) with the corresponding values from the passed map. The
|
||||
// values will be escaped (aka URL encoded) before insertion into the path.
|
||||
func WithEscapedPathParameters(path string, pathParameters map[string]interface{}) PrepareDecorator {
|
||||
parameters := escapeValueStrings(ensureValueStrings(pathParameters))
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
if r.URL == nil {
|
||||
return r, NewError("autorest", "WithEscapedPathParameters", "Invoked with a nil URL")
|
||||
}
|
||||
for key, value := range parameters {
|
||||
path = strings.Replace(path, "{"+key+"}", value, -1)
|
||||
}
|
||||
if r.URL, err = parseURL(r.URL, path); err != nil {
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithPathParameters returns a PrepareDecorator that replaces brace-enclosed keys within the
|
||||
// request path (i.e., http.Request.URL.Path) with the corresponding values from the passed map.
|
||||
func WithPathParameters(path string, pathParameters map[string]interface{}) PrepareDecorator {
|
||||
parameters := ensureValueStrings(pathParameters)
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
if r.URL == nil {
|
||||
return r, NewError("autorest", "WithPathParameters", "Invoked with a nil URL")
|
||||
}
|
||||
for key, value := range parameters {
|
||||
path = strings.Replace(path, "{"+key+"}", value, -1)
|
||||
}
|
||||
|
||||
if r.URL, err = parseURL(r.URL, path); err != nil {
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func parseURL(u *url.URL, path string) (*url.URL, error) {
|
||||
p := strings.TrimRight(u.String(), "/")
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
return url.Parse(p + path)
|
||||
}
|
||||
|
||||
// WithQueryParameters returns a PrepareDecorators that encodes and applies the query parameters
|
||||
// given in the supplied map (i.e., key=value).
|
||||
func WithQueryParameters(queryParameters map[string]interface{}) PrepareDecorator {
|
||||
parameters := ensureValueStrings(queryParameters)
|
||||
return func(p Preparer) Preparer {
|
||||
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
r, err := p.Prepare(r)
|
||||
if err == nil {
|
||||
if r.URL == nil {
|
||||
return r, NewError("autorest", "WithQueryParameters", "Invoked with a nil URL")
|
||||
}
|
||||
|
||||
v := r.URL.Query()
|
||||
for key, value := range parameters {
|
||||
d, err := url.QueryUnescape(value)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
v.Add(key, d)
|
||||
}
|
||||
r.URL.RawQuery = v.Encode()
|
||||
}
|
||||
return r, err
|
||||
})
|
||||
}
|
||||
}
|
||||
250
vendor/github.com/Azure/go-autorest/autorest/responder.go
generated
vendored
250
vendor/github.com/Azure/go-autorest/autorest/responder.go
generated
vendored
@@ -1,250 +0,0 @@
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Responder is the interface that wraps the Respond method.
|
||||
//
|
||||
// Respond accepts and reacts to an http.Response. Implementations must ensure to not share or hold
|
||||
// state since Responders may be shared and re-used.
|
||||
type Responder interface {
|
||||
Respond(*http.Response) error
|
||||
}
|
||||
|
||||
// ResponderFunc is a method that implements the Responder interface.
|
||||
type ResponderFunc func(*http.Response) error
|
||||
|
||||
// Respond implements the Responder interface on ResponderFunc.
|
||||
func (rf ResponderFunc) Respond(r *http.Response) error {
|
||||
return rf(r)
|
||||
}
|
||||
|
||||
// RespondDecorator takes and possibly decorates, by wrapping, a Responder. Decorators may react to
|
||||
// the http.Response and pass it along or, first, pass the http.Response along then react.
|
||||
type RespondDecorator func(Responder) Responder
|
||||
|
||||
// CreateResponder creates, decorates, and returns a Responder. Without decorators, the returned
|
||||
// Responder returns the passed http.Response unmodified. Responders may or may not be safe to share
|
||||
// and re-used: It depends on the applied decorators. For example, a standard decorator that closes
|
||||
// the response body is fine to share whereas a decorator that reads the body into a passed struct
|
||||
// is not.
|
||||
//
|
||||
// To prevent memory leaks, ensure that at least one Responder closes the response body.
|
||||
func CreateResponder(decorators ...RespondDecorator) Responder {
|
||||
return DecorateResponder(
|
||||
Responder(ResponderFunc(func(r *http.Response) error { return nil })),
|
||||
decorators...)
|
||||
}
|
||||
|
||||
// DecorateResponder accepts a Responder and a, possibly empty, set of RespondDecorators, which it
|
||||
// applies to the Responder. Decorators are applied in the order received, but their affect upon the
|
||||
// request depends on whether they are a pre-decorator (react to the http.Response and then pass it
|
||||
// along) or a post-decorator (pass the http.Response along and then react).
|
||||
func DecorateResponder(r Responder, decorators ...RespondDecorator) Responder {
|
||||
for _, decorate := range decorators {
|
||||
r = decorate(r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Respond accepts an http.Response and a, possibly empty, set of RespondDecorators.
|
||||
// It creates a Responder from the decorators it then applies to the passed http.Response.
|
||||
func Respond(r *http.Response, decorators ...RespondDecorator) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return CreateResponder(decorators...).Respond(r)
|
||||
}
|
||||
|
||||
// ByIgnoring returns a RespondDecorator that ignores the passed http.Response passing it unexamined
|
||||
// to the next RespondDecorator.
|
||||
func ByIgnoring() RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
return r.Respond(resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ByCopying copies the contents of the http.Response Body into the passed bytes.Buffer as
|
||||
// the Body is read.
|
||||
func ByCopying(b *bytes.Buffer) RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if err == nil && resp != nil && resp.Body != nil {
|
||||
resp.Body = TeeReadCloser(resp.Body, b)
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ByDiscardingBody returns a RespondDecorator that first invokes the passed Responder after which
|
||||
// it copies the remaining bytes (if any) in the response body to ioutil.Discard. Since the passed
|
||||
// Responder is invoked prior to discarding the response body, the decorator may occur anywhere
|
||||
// within the set.
|
||||
func ByDiscardingBody() RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if err == nil && resp != nil && resp.Body != nil {
|
||||
if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
|
||||
return fmt.Errorf("Error discarding the response body: %v", err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ByClosing returns a RespondDecorator that first invokes the passed Responder after which it
|
||||
// closes the response body. Since the passed Responder is invoked prior to closing the response
|
||||
// body, the decorator may occur anywhere within the set.
|
||||
func ByClosing() RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if resp != nil && resp.Body != nil {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
return fmt.Errorf("Error closing the response body: %v", err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ByClosingIfError returns a RespondDecorator that first invokes the passed Responder after which
|
||||
// it closes the response if the passed Responder returns an error and the response body exists.
|
||||
func ByClosingIfError() RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if err != nil && resp != nil && resp.Body != nil {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
return fmt.Errorf("Error closing the response body: %v", err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ByUnmarshallingJSON returns a RespondDecorator that decodes a JSON document returned in the
|
||||
// response Body into the value pointed to by v.
|
||||
func ByUnmarshallingJSON(v interface{}) RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if err == nil {
|
||||
b, errInner := ioutil.ReadAll(resp.Body)
|
||||
// Some responses might include a BOM, remove for successful unmarshalling
|
||||
b = bytes.TrimPrefix(b, []byte("\xef\xbb\xbf"))
|
||||
if errInner != nil {
|
||||
err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
|
||||
} else if len(strings.Trim(string(b), " ")) > 0 {
|
||||
errInner = json.Unmarshal(b, v)
|
||||
if errInner != nil {
|
||||
err = fmt.Errorf("Error occurred unmarshalling JSON - Error = '%v' JSON = '%s'", errInner, string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ByUnmarshallingXML returns a RespondDecorator that decodes a XML document returned in the
|
||||
// response Body into the value pointed to by v.
|
||||
func ByUnmarshallingXML(v interface{}) RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if err == nil {
|
||||
b, errInner := ioutil.ReadAll(resp.Body)
|
||||
if errInner != nil {
|
||||
err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
|
||||
} else {
|
||||
errInner = xml.Unmarshal(b, v)
|
||||
if errInner != nil {
|
||||
err = fmt.Errorf("Error occurred unmarshalling Xml - Error = '%v' Xml = '%s'", errInner, string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithErrorUnlessStatusCode returns a RespondDecorator that emits an error unless the response
|
||||
// StatusCode is among the set passed. On error, response body is fully read into a buffer and
|
||||
// presented in the returned error, as well as in the response body.
|
||||
func WithErrorUnlessStatusCode(codes ...int) RespondDecorator {
|
||||
return func(r Responder) Responder {
|
||||
return ResponderFunc(func(resp *http.Response) error {
|
||||
err := r.Respond(resp)
|
||||
if err == nil && !ResponseHasStatusCode(resp, codes...) {
|
||||
derr := NewErrorWithResponse("autorest", "WithErrorUnlessStatusCode", resp, "%v %v failed with %s",
|
||||
resp.Request.Method,
|
||||
resp.Request.URL,
|
||||
resp.Status)
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
derr.ServiceError = b
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(b))
|
||||
}
|
||||
err = derr
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithErrorUnlessOK returns a RespondDecorator that emits an error if the response StatusCode is
|
||||
// anything other than HTTP 200.
|
||||
func WithErrorUnlessOK() RespondDecorator {
|
||||
return WithErrorUnlessStatusCode(http.StatusOK)
|
||||
}
|
||||
|
||||
// ExtractHeader extracts all values of the specified header from the http.Response. It returns an
|
||||
// empty string slice if the passed http.Response is nil or the header does not exist.
|
||||
func ExtractHeader(header string, resp *http.Response) []string {
|
||||
if resp != nil && resp.Header != nil {
|
||||
return resp.Header[http.CanonicalHeaderKey(header)]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractHeaderValue extracts the first value of the specified header from the http.Response. It
|
||||
// returns an empty string if the passed http.Response is nil or the header does not exist.
|
||||
func ExtractHeaderValue(header string, resp *http.Response) string {
|
||||
h := ExtractHeader(header, resp)
|
||||
if len(h) > 0 {
|
||||
return h[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
52
vendor/github.com/Azure/go-autorest/autorest/retriablerequest.go
generated
vendored
52
vendor/github.com/Azure/go-autorest/autorest/retriablerequest.go
generated
vendored
@@ -1,52 +0,0 @@
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewRetriableRequest returns a wrapper around an HTTP request that support retry logic.
|
||||
func NewRetriableRequest(req *http.Request) *RetriableRequest {
|
||||
return &RetriableRequest{req: req}
|
||||
}
|
||||
|
||||
// Request returns the wrapped HTTP request.
|
||||
func (rr *RetriableRequest) Request() *http.Request {
|
||||
return rr.req
|
||||
}
|
||||
|
||||
func (rr *RetriableRequest) prepareFromByteReader() (err error) {
|
||||
// fall back to making a copy (only do this once)
|
||||
b := []byte{}
|
||||
if rr.req.ContentLength > 0 {
|
||||
b = make([]byte, rr.req.ContentLength)
|
||||
_, err = io.ReadFull(rr.req.Body, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
b, err = ioutil.ReadAll(rr.req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
rr.br = bytes.NewReader(b)
|
||||
rr.req.Body = ioutil.NopCloser(rr.br)
|
||||
return err
|
||||
}
|
||||
54
vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.7.go
generated
vendored
54
vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.7.go
generated
vendored
@@ -1,54 +0,0 @@
|
||||
// +build !go1.8
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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 autorest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// RetriableRequest provides facilities for retrying an HTTP request.
|
||||
type RetriableRequest struct {
|
||||
req *http.Request
|
||||
br *bytes.Reader
|
||||
}
|
||||
|
||||
// Prepare signals that the request is about to be sent.
|
||||
func (rr *RetriableRequest) Prepare() (err error) {
|
||||
// preserve the request body; this is to support retry logic as
|
||||
// the underlying transport will always close the reqeust body
|
||||
if rr.req.Body != nil {
|
||||
if rr.br != nil {
|
||||
_, err = rr.br.Seek(0, 0 /*io.SeekStart*/)
|
||||
rr.req.Body = ioutil.NopCloser(rr.br)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rr.br == nil {
|
||||
// fall back to making a copy (only do this once)
|
||||
err = rr.prepareFromByteReader()
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func removeRequestBody(req *http.Request) {
|
||||
req.Body = nil
|
||||
req.ContentLength = 0
|
||||
}
|
||||
66
vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.8.go
generated
vendored
66
vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.8.go
generated
vendored
@@ -1,66 +0,0 @@
|
||||
// +build go1.8
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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 autorest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// RetriableRequest provides facilities for retrying an HTTP request.
|
||||
type RetriableRequest struct {
|
||||
req *http.Request
|
||||
rc io.ReadCloser
|
||||
br *bytes.Reader
|
||||
}
|
||||
|
||||
// Prepare signals that the request is about to be sent.
|
||||
func (rr *RetriableRequest) Prepare() (err error) {
|
||||
// preserve the request body; this is to support retry logic as
|
||||
// the underlying transport will always close the reqeust body
|
||||
if rr.req.Body != nil {
|
||||
if rr.rc != nil {
|
||||
rr.req.Body = rr.rc
|
||||
} else if rr.br != nil {
|
||||
_, err = rr.br.Seek(0, io.SeekStart)
|
||||
rr.req.Body = ioutil.NopCloser(rr.br)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rr.req.GetBody != nil {
|
||||
// this will allow us to preserve the body without having to
|
||||
// make a copy. note we need to do this on each iteration
|
||||
rr.rc, err = rr.req.GetBody()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if rr.br == nil {
|
||||
// fall back to making a copy (only do this once)
|
||||
err = rr.prepareFromByteReader()
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func removeRequestBody(req *http.Request) {
|
||||
req.Body = nil
|
||||
req.GetBody = nil
|
||||
req.ContentLength = 0
|
||||
}
|
||||
325
vendor/github.com/Azure/go-autorest/autorest/sender.go
generated
vendored
325
vendor/github.com/Azure/go-autorest/autorest/sender.go
generated
vendored
@@ -1,325 +0,0 @@
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Sender is the interface that wraps the Do method to send HTTP requests.
|
||||
//
|
||||
// The standard http.Client conforms to this interface.
|
||||
type Sender interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// SenderFunc is a method that implements the Sender interface.
|
||||
type SenderFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
// Do implements the Sender interface on SenderFunc.
|
||||
func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) {
|
||||
return sf(r)
|
||||
}
|
||||
|
||||
// SendDecorator takes and possibily decorates, by wrapping, a Sender. Decorators may affect the
|
||||
// http.Request and pass it along or, first, pass the http.Request along then react to the
|
||||
// http.Response result.
|
||||
type SendDecorator func(Sender) Sender
|
||||
|
||||
// CreateSender creates, decorates, and returns, as a Sender, the default http.Client.
|
||||
func CreateSender(decorators ...SendDecorator) Sender {
|
||||
return DecorateSender(&http.Client{}, decorators...)
|
||||
}
|
||||
|
||||
// DecorateSender accepts a Sender and a, possibly empty, set of SendDecorators, which is applies to
|
||||
// the Sender. Decorators are applied in the order received, but their affect upon the request
|
||||
// depends on whether they are a pre-decorator (change the http.Request and then pass it along) or a
|
||||
// post-decorator (pass the http.Request along and react to the results in http.Response).
|
||||
func DecorateSender(s Sender, decorators ...SendDecorator) Sender {
|
||||
for _, decorate := range decorators {
|
||||
s = decorate(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Send sends, by means of the default http.Client, the passed http.Request, returning the
|
||||
// http.Response and possible error. It also accepts a, possibly empty, set of SendDecorators which
|
||||
// it will apply the http.Client before invoking the Do method.
|
||||
//
|
||||
// Send is a convenience method and not recommended for production. Advanced users should use
|
||||
// SendWithSender, passing and sharing their own Sender (e.g., instance of http.Client).
|
||||
//
|
||||
// Send will not poll or retry requests.
|
||||
func Send(r *http.Request, decorators ...SendDecorator) (*http.Response, error) {
|
||||
return SendWithSender(&http.Client{}, r, decorators...)
|
||||
}
|
||||
|
||||
// SendWithSender sends the passed http.Request, through the provided Sender, returning the
|
||||
// http.Response and possible error. It also accepts a, possibly empty, set of SendDecorators which
|
||||
// it will apply the http.Client before invoking the Do method.
|
||||
//
|
||||
// SendWithSender will not poll or retry requests.
|
||||
func SendWithSender(s Sender, r *http.Request, decorators ...SendDecorator) (*http.Response, error) {
|
||||
return DecorateSender(s, decorators...).Do(r)
|
||||
}
|
||||
|
||||
// AfterDelay returns a SendDecorator that delays for the passed time.Duration before
|
||||
// invoking the Sender. The delay may be terminated by closing the optional channel on the
|
||||
// http.Request. If canceled, no further Senders are invoked.
|
||||
func AfterDelay(d time.Duration) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if !DelayForBackoff(d, 0, r.Context().Done()) {
|
||||
return nil, fmt.Errorf("autorest: AfterDelay canceled before full delay")
|
||||
}
|
||||
return s.Do(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AsIs returns a SendDecorator that invokes the passed Sender without modifying the http.Request.
|
||||
func AsIs() SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return s.Do(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DoCloseIfError returns a SendDecorator that first invokes the passed Sender after which
|
||||
// it closes the response if the passed Sender returns an error and the response body exists.
|
||||
func DoCloseIfError() SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
resp, err := s.Do(r)
|
||||
if err != nil {
|
||||
Respond(resp, ByDiscardingBody(), ByClosing())
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DoErrorIfStatusCode returns a SendDecorator that emits an error if the response StatusCode is
|
||||
// among the set passed. Since these are artificial errors, the response body may still require
|
||||
// closing.
|
||||
func DoErrorIfStatusCode(codes ...int) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
resp, err := s.Do(r)
|
||||
if err == nil && ResponseHasStatusCode(resp, codes...) {
|
||||
err = NewErrorWithResponse("autorest", "DoErrorIfStatusCode", resp, "%v %v failed with %s",
|
||||
resp.Request.Method,
|
||||
resp.Request.URL,
|
||||
resp.Status)
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DoErrorUnlessStatusCode returns a SendDecorator that emits an error unless the response
|
||||
// StatusCode is among the set passed. Since these are artificial errors, the response body
|
||||
// may still require closing.
|
||||
func DoErrorUnlessStatusCode(codes ...int) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
resp, err := s.Do(r)
|
||||
if err == nil && !ResponseHasStatusCode(resp, codes...) {
|
||||
err = NewErrorWithResponse("autorest", "DoErrorUnlessStatusCode", resp, "%v %v failed with %s",
|
||||
resp.Request.Method,
|
||||
resp.Request.URL,
|
||||
resp.Status)
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DoPollForStatusCodes returns a SendDecorator that polls if the http.Response contains one of the
|
||||
// passed status codes. It expects the http.Response to contain a Location header providing the
|
||||
// URL at which to poll (using GET) and will poll until the time passed is equal to or greater than
|
||||
// the supplied duration. It will delay between requests for the duration specified in the
|
||||
// RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled by
|
||||
// closing the optional channel on the http.Request.
|
||||
func DoPollForStatusCodes(duration time.Duration, delay time.Duration, codes ...int) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
||||
resp, err = s.Do(r)
|
||||
|
||||
if err == nil && ResponseHasStatusCode(resp, codes...) {
|
||||
r, err = NewPollingRequestWithContext(r.Context(), resp)
|
||||
|
||||
for err == nil && ResponseHasStatusCode(resp, codes...) {
|
||||
Respond(resp,
|
||||
ByDiscardingBody(),
|
||||
ByClosing())
|
||||
resp, err = SendWithSender(s, r,
|
||||
AfterDelay(GetRetryAfter(resp, delay)))
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DoRetryForAttempts returns a SendDecorator that retries a failed request for up to the specified
|
||||
// number of attempts, exponentially backing off between requests using the supplied backoff
|
||||
// time.Duration (which may be zero). Retrying may be canceled by closing the optional channel on
|
||||
// the http.Request.
|
||||
func DoRetryForAttempts(attempts int, backoff time.Duration) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
||||
rr := NewRetriableRequest(r)
|
||||
for attempt := 0; attempt < attempts; attempt++ {
|
||||
err = rr.Prepare()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp, err = s.Do(rr.Request())
|
||||
if err == nil {
|
||||
return resp, err
|
||||
}
|
||||
if !DelayForBackoff(backoff, attempt, r.Context().Done()) {
|
||||
return nil, r.Context().Err()
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DoRetryForStatusCodes returns a SendDecorator that retries for specified statusCodes for up to the specified
|
||||
// number of attempts, exponentially backing off between requests using the supplied backoff
|
||||
// time.Duration (which may be zero). Retrying may be canceled by closing the optional channel on
|
||||
// the http.Request.
|
||||
func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
||||
rr := NewRetriableRequest(r)
|
||||
// Increment to add the first call (attempts denotes number of retries)
|
||||
attempts++
|
||||
for attempt := 0; attempt < attempts; {
|
||||
err = rr.Prepare()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp, err = s.Do(rr.Request())
|
||||
// if the error isn't temporary don't bother retrying
|
||||
if err != nil && !IsTemporaryNetworkError(err) {
|
||||
return nil, err
|
||||
}
|
||||
// we want to retry if err is not nil (e.g. transient network failure). note that for failed authentication
|
||||
// resp and err will both have a value, so in this case we don't want to retry as it will never succeed.
|
||||
if err == nil && !ResponseHasStatusCode(resp, codes...) || IsTokenRefreshError(err) {
|
||||
return resp, err
|
||||
}
|
||||
delayed := DelayWithRetryAfter(resp, r.Context().Done())
|
||||
if !delayed && !DelayForBackoff(backoff, attempt, r.Context().Done()) {
|
||||
return nil, r.Context().Err()
|
||||
}
|
||||
// don't count a 429 against the number of attempts
|
||||
// so that we continue to retry until it succeeds
|
||||
if resp == nil || resp.StatusCode != http.StatusTooManyRequests {
|
||||
attempt++
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DelayWithRetryAfter invokes time.After for the duration specified in the "Retry-After" header in
|
||||
// responses with status code 429
|
||||
func DelayWithRetryAfter(resp *http.Response, cancel <-chan struct{}) bool {
|
||||
if resp == nil {
|
||||
return false
|
||||
}
|
||||
retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
|
||||
if resp.StatusCode == http.StatusTooManyRequests && retryAfter > 0 {
|
||||
select {
|
||||
case <-time.After(time.Duration(retryAfter) * time.Second):
|
||||
return true
|
||||
case <-cancel:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DoRetryForDuration returns a SendDecorator that retries the request until the total time is equal
|
||||
// to or greater than the specified duration, exponentially backing off between requests using the
|
||||
// supplied backoff time.Duration (which may be zero). Retrying may be canceled by closing the
|
||||
// optional channel on the http.Request.
|
||||
func DoRetryForDuration(d time.Duration, backoff time.Duration) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
||||
rr := NewRetriableRequest(r)
|
||||
end := time.Now().Add(d)
|
||||
for attempt := 0; time.Now().Before(end); attempt++ {
|
||||
err = rr.Prepare()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp, err = s.Do(rr.Request())
|
||||
if err == nil {
|
||||
return resp, err
|
||||
}
|
||||
if !DelayForBackoff(backoff, attempt, r.Context().Done()) {
|
||||
return nil, r.Context().Err()
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogging returns a SendDecorator that implements simple before and after logging of the
|
||||
// request.
|
||||
func WithLogging(logger *log.Logger) SendDecorator {
|
||||
return func(s Sender) Sender {
|
||||
return SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
logger.Printf("Sending %s %s", r.Method, r.URL)
|
||||
resp, err := s.Do(r)
|
||||
if err != nil {
|
||||
logger.Printf("%s %s received error '%v'", r.Method, r.URL, err)
|
||||
} else {
|
||||
logger.Printf("%s %s received %s", r.Method, r.URL, resp.Status)
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DelayForBackoff invokes time.After for the supplied backoff duration raised to the power of
|
||||
// passed attempt (i.e., an exponential backoff delay). Backoff duration is in seconds and can set
|
||||
// to zero for no delay. The delay may be canceled by closing the passed channel. If terminated early,
|
||||
// returns false.
|
||||
// Note: Passing attempt 1 will result in doubling "backoff" duration. Treat this as a zero-based attempt
|
||||
// count.
|
||||
func DelayForBackoff(backoff time.Duration, attempt int, cancel <-chan struct{}) bool {
|
||||
select {
|
||||
case <-time.After(time.Duration(backoff.Seconds()*math.Pow(2, float64(attempt))) * time.Second):
|
||||
return true
|
||||
case <-cancel:
|
||||
return false
|
||||
}
|
||||
}
|
||||
228
vendor/github.com/Azure/go-autorest/autorest/utility.go
generated
vendored
228
vendor/github.com/Azure/go-autorest/autorest/utility.go
generated
vendored
@@ -1,228 +0,0 @@
|
||||
package autorest
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
)
|
||||
|
||||
// EncodedAs is a series of constants specifying various data encodings
|
||||
type EncodedAs string
|
||||
|
||||
const (
|
||||
// EncodedAsJSON states that data is encoded as JSON
|
||||
EncodedAsJSON EncodedAs = "JSON"
|
||||
|
||||
// EncodedAsXML states that data is encoded as Xml
|
||||
EncodedAsXML EncodedAs = "XML"
|
||||
)
|
||||
|
||||
// Decoder defines the decoding method json.Decoder and xml.Decoder share
|
||||
type Decoder interface {
|
||||
Decode(v interface{}) error
|
||||
}
|
||||
|
||||
// NewDecoder creates a new decoder appropriate to the passed encoding.
|
||||
// encodedAs specifies the type of encoding and r supplies the io.Reader containing the
|
||||
// encoded data.
|
||||
func NewDecoder(encodedAs EncodedAs, r io.Reader) Decoder {
|
||||
if encodedAs == EncodedAsJSON {
|
||||
return json.NewDecoder(r)
|
||||
} else if encodedAs == EncodedAsXML {
|
||||
return xml.NewDecoder(r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyAndDecode decodes the data from the passed io.Reader while making a copy. Having a copy
|
||||
// is especially useful if there is a chance the data will fail to decode.
|
||||
// encodedAs specifies the expected encoding, r provides the io.Reader to the data, and v
|
||||
// is the decoding destination.
|
||||
func CopyAndDecode(encodedAs EncodedAs, r io.Reader, v interface{}) (bytes.Buffer, error) {
|
||||
b := bytes.Buffer{}
|
||||
return b, NewDecoder(encodedAs, io.TeeReader(r, &b)).Decode(v)
|
||||
}
|
||||
|
||||
// TeeReadCloser returns a ReadCloser that writes to w what it reads from rc.
|
||||
// It utilizes io.TeeReader to copy the data read and has the same behavior when reading.
|
||||
// Further, when it is closed, it ensures that rc is closed as well.
|
||||
func TeeReadCloser(rc io.ReadCloser, w io.Writer) io.ReadCloser {
|
||||
return &teeReadCloser{rc, io.TeeReader(rc, w)}
|
||||
}
|
||||
|
||||
type teeReadCloser struct {
|
||||
rc io.ReadCloser
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (t *teeReadCloser) Read(p []byte) (int, error) {
|
||||
return t.r.Read(p)
|
||||
}
|
||||
|
||||
func (t *teeReadCloser) Close() error {
|
||||
return t.rc.Close()
|
||||
}
|
||||
|
||||
func containsInt(ints []int, n int) bool {
|
||||
for _, i := range ints {
|
||||
if i == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func escapeValueStrings(m map[string]string) map[string]string {
|
||||
for key, value := range m {
|
||||
m[key] = url.QueryEscape(value)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func ensureValueStrings(mapOfInterface map[string]interface{}) map[string]string {
|
||||
mapOfStrings := make(map[string]string)
|
||||
for key, value := range mapOfInterface {
|
||||
mapOfStrings[key] = ensureValueString(value)
|
||||
}
|
||||
return mapOfStrings
|
||||
}
|
||||
|
||||
func ensureValueString(value interface{}) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return string(v)
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
// MapToValues method converts map[string]interface{} to url.Values.
|
||||
func MapToValues(m map[string]interface{}) url.Values {
|
||||
v := url.Values{}
|
||||
for key, value := range m {
|
||||
x := reflect.ValueOf(value)
|
||||
if x.Kind() == reflect.Array || x.Kind() == reflect.Slice {
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
v.Add(key, ensureValueString(x.Index(i)))
|
||||
}
|
||||
} else {
|
||||
v.Add(key, ensureValueString(value))
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// AsStringSlice method converts interface{} to []string. This expects a
|
||||
//that the parameter passed to be a slice or array of a type that has the underlying
|
||||
//type a string.
|
||||
func AsStringSlice(s interface{}) ([]string, error) {
|
||||
v := reflect.ValueOf(s)
|
||||
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
||||
return nil, NewError("autorest", "AsStringSlice", "the value's type is not an array.")
|
||||
}
|
||||
stringSlice := make([]string, 0, v.Len())
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
stringSlice = append(stringSlice, v.Index(i).String())
|
||||
}
|
||||
return stringSlice, nil
|
||||
}
|
||||
|
||||
// String method converts interface v to string. If interface is a list, it
|
||||
// joins list elements using the seperator. Note that only sep[0] will be used for
|
||||
// joining if any separator is specified.
|
||||
func String(v interface{}, sep ...string) string {
|
||||
if len(sep) == 0 {
|
||||
return ensureValueString(v)
|
||||
}
|
||||
stringSlice, ok := v.([]string)
|
||||
if ok == false {
|
||||
var err error
|
||||
stringSlice, err = AsStringSlice(v)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("autorest: Couldn't convert value to a string %s.", err))
|
||||
}
|
||||
}
|
||||
return ensureValueString(strings.Join(stringSlice, sep[0]))
|
||||
}
|
||||
|
||||
// Encode method encodes url path and query parameters.
|
||||
func Encode(location string, v interface{}, sep ...string) string {
|
||||
s := String(v, sep...)
|
||||
switch strings.ToLower(location) {
|
||||
case "path":
|
||||
return pathEscape(s)
|
||||
case "query":
|
||||
return queryEscape(s)
|
||||
default:
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func pathEscape(s string) string {
|
||||
return strings.Replace(url.QueryEscape(s), "+", "%20", -1)
|
||||
}
|
||||
|
||||
func queryEscape(s string) string {
|
||||
return url.QueryEscape(s)
|
||||
}
|
||||
|
||||
// ChangeToGet turns the specified http.Request into a GET (it assumes it wasn't).
|
||||
// This is mainly useful for long-running operations that use the Azure-AsyncOperation
|
||||
// header, so we change the initial PUT into a GET to retrieve the final result.
|
||||
func ChangeToGet(req *http.Request) *http.Request {
|
||||
req.Method = "GET"
|
||||
req.Body = nil
|
||||
req.ContentLength = 0
|
||||
req.Header.Del("Content-Length")
|
||||
return req
|
||||
}
|
||||
|
||||
// IsTokenRefreshError returns true if the specified error implements the TokenRefreshError
|
||||
// interface. If err is a DetailedError it will walk the chain of Original errors.
|
||||
func IsTokenRefreshError(err error) bool {
|
||||
if _, ok := err.(adal.TokenRefreshError); ok {
|
||||
return true
|
||||
}
|
||||
if de, ok := err.(DetailedError); ok {
|
||||
return IsTokenRefreshError(de.Original)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsTemporaryNetworkError returns true if the specified error is a temporary network error or false
|
||||
// if it's not. If the error doesn't implement the net.Error interface the return value is true.
|
||||
func IsTemporaryNetworkError(err error) bool {
|
||||
if netErr, ok := err.(net.Error); !ok || (ok && netErr.Temporary()) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
22
vendor/github.com/Azure/go-autorest/autorest/version.go
generated
vendored
22
vendor/github.com/Azure/go-autorest/autorest/version.go
generated
vendored
@@ -1,22 +0,0 @@
|
||||
package autorest
|
||||
|
||||
import "github.com/Azure/go-autorest/version"
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Version returns the semantic version (see http://semver.org).
|
||||
func Version() string {
|
||||
return version.Number
|
||||
}
|
||||
37
vendor/github.com/Azure/go-autorest/version/version.go
generated
vendored
37
vendor/github.com/Azure/go-autorest/version/version.go
generated
vendored
@@ -1,37 +0,0 @@
|
||||
package version
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Number contains the semantic version of this SDK.
|
||||
const Number = "v10.14.0"
|
||||
|
||||
var (
|
||||
userAgent = fmt.Sprintf("Go/%s (%s-%s) go-autorest/%s",
|
||||
runtime.Version(),
|
||||
runtime.GOARCH,
|
||||
runtime.GOOS,
|
||||
Number,
|
||||
)
|
||||
)
|
||||
|
||||
// UserAgent returns a string containing the Go version, system archityecture and OS, and the go-autorest version.
|
||||
func UserAgent() string {
|
||||
return userAgent
|
||||
}
|
||||
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
@@ -1,15 +0,0 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
152
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
152
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
@@ -1,152 +0,0 @@
|
||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build !js,!appengine,!safe,!disableunsafe
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
var (
|
||||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
||||
// internal reflect.Value fields. These values are valid before golang
|
||||
// commit ecccf07e7f9d which changed the format. The are also valid
|
||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||
// the original format. Code in the init function updates these offsets
|
||||
// as necessary.
|
||||
offsetPtr = uintptr(ptrSize)
|
||||
offsetScalar = uintptr(0)
|
||||
offsetFlag = uintptr(ptrSize * 2)
|
||||
|
||||
// flagKindWidth and flagKindShift indicate various bits that the
|
||||
// reflect package uses internally to track kind information.
|
||||
//
|
||||
// flagRO indicates whether or not the value field of a reflect.Value is
|
||||
// read-only.
|
||||
//
|
||||
// flagIndir indicates whether the value field of a reflect.Value is
|
||||
// the actual data or a pointer to the data.
|
||||
//
|
||||
// These values are valid before golang commit 90a7c3c86944 which
|
||||
// changed their positions. Code in the init function updates these
|
||||
// flags as necessary.
|
||||
flagKindWidth = uintptr(5)
|
||||
flagKindShift = uintptr(flagKindWidth - 1)
|
||||
flagRO = uintptr(1 << 0)
|
||||
flagIndir = uintptr(1 << 1)
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Older versions of reflect.Value stored small integers directly in the
|
||||
// ptr field (which is named val in the older versions). Versions
|
||||
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
||||
// scalar for this purpose which unfortunately came before the flag
|
||||
// field, so the offset of the flag field is different for those
|
||||
// versions.
|
||||
//
|
||||
// This code constructs a new reflect.Value from a known small integer
|
||||
// and checks if the size of the reflect.Value struct indicates it has
|
||||
// the scalar field. When it does, the offsets are updated accordingly.
|
||||
vv := reflect.ValueOf(0xf00)
|
||||
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
||||
offsetScalar = ptrSize * 2
|
||||
offsetFlag = ptrSize * 3
|
||||
}
|
||||
|
||||
// Commit 90a7c3c86944 changed the flag positions such that the low
|
||||
// order bits are the kind. This code extracts the kind from the flags
|
||||
// field and ensures it's the correct type. When it's not, the flag
|
||||
// order has been changed to the newer format, so the flags are updated
|
||||
// accordingly.
|
||||
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
||||
upfv := *(*uintptr)(upf)
|
||||
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
||||
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
||||
flagKindShift = 0
|
||||
flagRO = 1 << 5
|
||||
flagIndir = 1 << 6
|
||||
|
||||
// Commit adf9b30e5594 modified the flags to separate the
|
||||
// flagRO flag into two bits which specifies whether or not the
|
||||
// field is embedded. This causes flagIndir to move over a bit
|
||||
// and means that flagRO is the combination of either of the
|
||||
// original flagRO bit and the new bit.
|
||||
//
|
||||
// This code detects the change by extracting what used to be
|
||||
// the indirect bit to ensure it's set. When it's not, the flag
|
||||
// order has been changed to the newer format, so the flags are
|
||||
// updated accordingly.
|
||||
if upfv&flagIndir == 0 {
|
||||
flagRO = 3 << 5
|
||||
flagIndir = 1 << 7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
||||
indirects := 1
|
||||
vt := v.Type()
|
||||
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
||||
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
||||
if rvf&flagIndir != 0 {
|
||||
vt = reflect.PtrTo(v.Type())
|
||||
indirects++
|
||||
} else if offsetScalar != 0 {
|
||||
// The value is in the scalar field when it's not one of the
|
||||
// reference types.
|
||||
switch vt.Kind() {
|
||||
case reflect.Uintptr:
|
||||
case reflect.Chan:
|
||||
case reflect.Func:
|
||||
case reflect.Map:
|
||||
case reflect.Ptr:
|
||||
case reflect.UnsafePointer:
|
||||
default:
|
||||
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
||||
offsetScalar)
|
||||
}
|
||||
}
|
||||
|
||||
pv := reflect.NewAt(vt, upv)
|
||||
rv = pv
|
||||
for i := 0; i < indirects; i++ {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
return rv
|
||||
}
|
||||
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
||||
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
@@ -1,341 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
panicBytes = []byte("(PANIC=")
|
||||
plusBytes = []byte("+")
|
||||
iBytes = []byte("i")
|
||||
trueBytes = []byte("true")
|
||||
falseBytes = []byte("false")
|
||||
interfaceBytes = []byte("(interface {})")
|
||||
commaNewlineBytes = []byte(",\n")
|
||||
newlineBytes = []byte("\n")
|
||||
openBraceBytes = []byte("{")
|
||||
openBraceNewlineBytes = []byte("{\n")
|
||||
closeBraceBytes = []byte("}")
|
||||
asteriskBytes = []byte("*")
|
||||
colonBytes = []byte(":")
|
||||
colonSpaceBytes = []byte(": ")
|
||||
openParenBytes = []byte("(")
|
||||
closeParenBytes = []byte(")")
|
||||
spaceBytes = []byte(" ")
|
||||
pointerChainBytes = []byte("->")
|
||||
nilAngleBytes = []byte("<nil>")
|
||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
percentBytes = []byte("%")
|
||||
precisionBytes = []byte(".")
|
||||
openAngleBytes = []byte("<")
|
||||
closeAngleBytes = []byte(">")
|
||||
openMapBytes = []byte("map[")
|
||||
closeMapBytes = []byte("]")
|
||||
lenEqualsBytes = []byte("len=")
|
||||
capEqualsBytes = []byte("cap=")
|
||||
)
|
||||
|
||||
// hexDigits is used to map a decimal value to a hex digit.
|
||||
var hexDigits = "0123456789abcdef"
|
||||
|
||||
// catchPanic handles any panics that might occur during the handleMethods
|
||||
// calls.
|
||||
func catchPanic(w io.Writer, v reflect.Value) {
|
||||
if err := recover(); err != nil {
|
||||
w.Write(panicBytes)
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMethods attempts to call the Error and String methods on the underlying
|
||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||
//
|
||||
// It handles panics in any called methods by catching and displaying the error
|
||||
// as the formatted value.
|
||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
// Choose whether or not to do error and Stringer interface lookups against
|
||||
// the base type or a pointer to the base type depending on settings.
|
||||
// Technically calling one of these methods with a pointer receiver can
|
||||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.Error()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
|
||||
w.Write([]byte(iface.Error()))
|
||||
return true
|
||||
|
||||
case fmt.Stringer:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.String()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
w.Write([]byte(iface.String()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printBool outputs a boolean value as true or false to Writer w.
|
||||
func printBool(w io.Writer, val bool) {
|
||||
if val {
|
||||
w.Write(trueBytes)
|
||||
} else {
|
||||
w.Write(falseBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// printInt outputs a signed integer value to Writer w.
|
||||
func printInt(w io.Writer, val int64, base int) {
|
||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||
}
|
||||
|
||||
// printUint outputs an unsigned integer value to Writer w.
|
||||
func printUint(w io.Writer, val uint64, base int) {
|
||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||
}
|
||||
|
||||
// printFloat outputs a floating point value using the specified precision,
|
||||
// which is expected to be 32 or 64bit, to Writer w.
|
||||
func printFloat(w io.Writer, val float64, precision int) {
|
||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||
}
|
||||
|
||||
// printComplex outputs a complex value using the specified float precision
|
||||
// for the real and imaginary parts to Writer w.
|
||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
r := real(c)
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||
i := imag(c)
|
||||
if i >= 0 {
|
||||
w.Write(plusBytes)
|
||||
}
|
||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||
w.Write(iBytes)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
num := uint64(p)
|
||||
if num == 0 {
|
||||
w.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||
buf := make([]byte, 18)
|
||||
|
||||
// It's simpler to construct the hex string right to left.
|
||||
base := uint64(16)
|
||||
i := len(buf) - 1
|
||||
for num >= base {
|
||||
buf[i] = hexDigits[num%base]
|
||||
num /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[num]
|
||||
|
||||
// Add '0x' prefix.
|
||||
i--
|
||||
buf[i] = 'x'
|
||||
i--
|
||||
buf[i] = '0'
|
||||
|
||||
// Strip unused leading bytes.
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
values []reflect.Value
|
||||
strings []string // either nil or same len and values
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||
// surrogate keys on which the data should be sorted. It uses flags in
|
||||
// ConfigState to decide if and how to populate those surrogate keys.
|
||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||
vs := &valuesSorter{values: values, cs: cs}
|
||||
if canSortSimply(vs.values[0].Kind()) {
|
||||
return vs
|
||||
}
|
||||
if !cs.DisableMethods {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
b := bytes.Buffer{}
|
||||
if !handleMethods(cs, &b, vs.values[i]) {
|
||||
vs.strings = nil
|
||||
break
|
||||
}
|
||||
vs.strings[i] = b.String()
|
||||
}
|
||||
}
|
||||
if vs.strings == nil && cs.SpewKeys {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||
// directly, or whether it should be considered for sorting by surrogate keys
|
||||
// (if the ConfigState allows it).
|
||||
func canSortSimply(kind reflect.Kind) bool {
|
||||
// This switch parallels valueSortLess, except for the default case.
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Uintptr:
|
||||
return true
|
||||
case reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
// Swap swaps the values at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
if s.strings != nil {
|
||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
// valueSortLess returns whether the first value should sort before the second
|
||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||
// implementation.
|
||||
func valueSortLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.String:
|
||||
return a.String() < b.String()
|
||||
case reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Array:
|
||||
// Compare the contents of both arrays.
|
||||
l := a.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
av := a.Index(i)
|
||||
bv := b.Index(i)
|
||||
if av.Interface() == bv.Interface() {
|
||||
continue
|
||||
}
|
||||
return valueSortLess(av, bv)
|
||||
}
|
||||
}
|
||||
return a.String() < b.String()
|
||||
}
|
||||
|
||||
// Less returns whether the value at index i should sort before the
|
||||
// value at index j. It is part of the sort.Interface implementation.
|
||||
func (s *valuesSorter) Less(i, j int) bool {
|
||||
if s.strings == nil {
|
||||
return valueSortLess(s.values[i], s.values[j])
|
||||
}
|
||||
return s.strings[i] < s.strings[j]
|
||||
}
|
||||
|
||||
// sortValues is a sort function that handles both native types and any type that
|
||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||
// their Value.String() value to ensure display stability.
|
||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Sort(newValuesSorter(values, cs))
|
||||
}
|
||||
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
@@ -1,306 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigState houses the configuration options used by spew to format and
|
||||
// display values. There is a global instance, Config, that is used to control
|
||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||
// provides methods equivalent to the top-level functions.
|
||||
//
|
||||
// The zero value for ConfigState provides no indentation. You would typically
|
||||
// want to set it to a space or a tab.
|
||||
//
|
||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||
// with default settings. See the documentation of NewDefaultConfig for default
|
||||
// values.
|
||||
type ConfigState struct {
|
||||
// Indent specifies the string to use for each indentation level. The
|
||||
// global config instance that all top-level functions use set this to a
|
||||
// single space by default. If you would like more indentation, you might
|
||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||
Indent string
|
||||
|
||||
// MaxDepth controls the maximum number of levels to descend into nested
|
||||
// data structures. The default, 0, means there is no limit.
|
||||
//
|
||||
// NOTE: Circular data structures are properly detected, so it is not
|
||||
// necessary to set this value unless you specifically want to limit deeply
|
||||
// nested data structures.
|
||||
MaxDepth int
|
||||
|
||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||
// invoked for types that implement them.
|
||||
DisableMethods bool
|
||||
|
||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||
// error and Stringer interfaces on types which only accept a pointer
|
||||
// receiver when the current type is not a pointer.
|
||||
//
|
||||
// NOTE: This might be an unsafe action since calling one of these methods
|
||||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "safe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// DisablePointerAddresses specifies whether to disable the printing of
|
||||
// pointer addresses. This is useful when diffing data structures in tests.
|
||||
DisablePointerAddresses bool
|
||||
|
||||
// DisableCapacities specifies whether to disable the printing of capacities
|
||||
// for arrays, slices, maps and channels. This is useful when diffing
|
||||
// data structures in tests.
|
||||
DisableCapacities bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
// a custom error or Stringer interface is invoked. The default, false,
|
||||
// means it will print the results of invoking the custom error or Stringer
|
||||
// interface and return immediately instead of continuing to recurse into
|
||||
// the internals of the data type.
|
||||
//
|
||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||
// via the DisableMethods or DisablePointerMethods options.
|
||||
ContinueOnMethod bool
|
||||
|
||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||
// this to have a more deterministic, diffable output. Note that only
|
||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||
// that support the error or Stringer interfaces (if methods are
|
||||
// enabled) are supported, with other types sorted according to the
|
||||
// reflect.Value.String() output which guarantees display stability.
|
||||
SortKeys bool
|
||||
|
||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
// The configuration can be changed by modifying the contents of spew.Config.
|
||||
var Config = ConfigState{Indent: " "}
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the formatted string as a value that satisfies error. See NewFormatter
|
||||
// for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
c.Printf, c.Println, or c.Printf.
|
||||
*/
|
||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(c, v)
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(c, w, a...)
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by modifying the public members
|
||||
of c. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func (c *ConfigState) Dump(a ...interface{}) {
|
||||
fdump(c, os.Stdout, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(c, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a spew Formatter interface using
|
||||
// the ConfigState associated with s.
|
||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = newFormatter(c, arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||
//
|
||||
// Indent: " "
|
||||
// MaxDepth: 0
|
||||
// DisableMethods: false
|
||||
// DisablePointerMethods: false
|
||||
// ContinueOnMethod: false
|
||||
// SortKeys: false
|
||||
func NewDefaultConfig() *ConfigState {
|
||||
return &ConfigState{Indent: " "}
|
||||
}
|
||||
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging.
|
||||
|
||||
A quick overview of the additional features spew provides over the built-in
|
||||
printing facilities for Go data types are as follows:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output (only when using
|
||||
Dump style)
|
||||
|
||||
There are two different approaches spew allows for dumping Go data structures:
|
||||
|
||||
* Dump style which prints with newlines, customizable indentation,
|
||||
and additional debug information such as types and all pointer addresses
|
||||
used to indirect to the final value
|
||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||
similar to the default %v while providing the additional functionality
|
||||
outlined above and passing unsupported format verbs such as %x and %q
|
||||
along to fmt
|
||||
|
||||
Quick Start
|
||||
|
||||
This section demonstrates how to quickly get started with spew. See the
|
||||
sections below for further details on formatting and configuration options.
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||
%#+v (adds types and pointer addresses):
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available
|
||||
via the spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
The following configuration options are available:
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of
|
||||
capacities for arrays, slices, maps and channels. This is useful when
|
||||
diffing data structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are
|
||||
supported with other types sorted according to the
|
||||
reflect.Value.String() output which guarantees display
|
||||
stability. Natural map order is used by default.
|
||||
|
||||
* SpewKeys
|
||||
Specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only
|
||||
considered if SortKeys is true.
|
||||
|
||||
Dump Usage
|
||||
|
||||
Simply call spew.Dump with a list of variables you want to dump:
|
||||
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
|
||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||
io.Writer. For example, to dump to standard error:
|
||||
|
||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||
|
||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Sample Dump Output
|
||||
|
||||
See the Dump example for details on the setup of the types and variables being
|
||||
shown here.
|
||||
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
(string) (len=3) "one": (bool) true
|
||||
}
|
||||
}
|
||||
|
||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||
command as shown.
|
||||
([]uint8) (len=32 cap=32) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
|
||||
Custom Formatter
|
||||
|
||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||
so that it integrates cleanly with standard fmt package printing functions. The
|
||||
formatter is useful for inline printing of smaller data types similar to the
|
||||
standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Custom Formatter Usage
|
||||
|
||||
The simplest way to make use of the spew custom formatter is to call one of the
|
||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||
functions have syntax you are most likely already familiar with:
|
||||
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Println(myVar, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
See the Index for the full list convenience functions.
|
||||
|
||||
Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
|
||||
See the Printf example for details on the setup of variables being shown
|
||||
here.
|
||||
|
||||
Errors
|
||||
|
||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||
detects them and handles them internally by printing the panic information
|
||||
inline with the output. Since spew is intended to provide deep pretty printing
|
||||
capabilities on structures, it intentionally does not return any errors.
|
||||
*/
|
||||
package spew
|
||||
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
@@ -1,509 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||
// convert cgo types to uint8 slices for hexdumping.
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
type dumpState struct {
|
||||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
// option.
|
||||
func (d *dumpState) indent() {
|
||||
if d.ignoreNextIndent {
|
||||
d.ignoreNextIndent = false
|
||||
return
|
||||
}
|
||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range d.pointers {
|
||||
if depth >= d.depth {
|
||||
delete(d.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by dereferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type information.
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
d.w.Write([]byte(ve.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
|
||||
// Display pointer information.
|
||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound == true:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound == true:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||
// Determine whether this type should be hex dumped or not. Also,
|
||||
// for types which should be hexdumped, try to use the underlying data
|
||||
// first, then fall back to trying to convert them to a uint8 slice.
|
||||
var buf []uint8
|
||||
doConvert := false
|
||||
doHexDump := false
|
||||
numEntries := v.Len()
|
||||
if numEntries > 0 {
|
||||
vt := v.Index(0).Type()
|
||||
vts := vt.String()
|
||||
switch {
|
||||
// C types that need to be converted.
|
||||
case cCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUnsignedCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUint8tCharRE.MatchString(vts):
|
||||
doConvert = true
|
||||
|
||||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
// be type asserted to a uint8 slice.
|
||||
doConvert = true
|
||||
}
|
||||
|
||||
// Copy and convert the underlying type if needed.
|
||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||
// Convert and copy each element into a uint8 byte
|
||||
// slice.
|
||||
buf = make([]uint8, numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
vv := v.Index(i)
|
||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||
}
|
||||
doHexDump = true
|
||||
}
|
||||
}
|
||||
|
||||
// Hexdump the entire slice as needed.
|
||||
if doHexDump {
|
||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||
str := indent + hex.Dump(buf)
|
||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||
str = strings.TrimRight(str, d.cs.Indent)
|
||||
d.w.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively call dump for each item.
|
||||
for i := 0; i < numEntries; i++ {
|
||||
d.dump(d.unpackValue(v.Index(i)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||
// value to figure out what kind of object we are dealing with and formats it
|
||||
// appropriately. It is a recursive function, however circular data structures
|
||||
// are detected and handled properly.
|
||||
func (d *dumpState) dump(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
d.w.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
d.indent()
|
||||
d.dumpPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !d.ignoreNextType {
|
||||
d.indent()
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write([]byte(v.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.ignoreNextType = false
|
||||
|
||||
// Display length and capacity if the built-in len and cap functions
|
||||
// work with the value's kind and the len/cap itself is non-zero.
|
||||
valueLen, valueCap := 0, 0
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||
valueLen, valueCap = v.Len(), v.Cap()
|
||||
case reflect.Map, reflect.String:
|
||||
valueLen = v.Len()
|
||||
}
|
||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
if valueLen != 0 {
|
||||
d.w.Write(lenEqualsBytes)
|
||||
printInt(d.w, int64(valueLen), 10)
|
||||
}
|
||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||
if valueLen != 0 {
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.w.Write(capEqualsBytes)
|
||||
printInt(d.w, int64(valueCap), 10)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||
// is enabled
|
||||
if !d.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(d.w, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(d.w, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(d.w, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(d.w, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(d.w, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(d.w, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(d.w, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
d.dumpSlice(v)
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.String:
|
||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
keys := v.MapKeys()
|
||||
if d.cs.SortKeys {
|
||||
sortValues(keys, d.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
d.dump(d.unpackValue(key))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
numFields := v.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
d.indent()
|
||||
vtf := vt.Field(i)
|
||||
d.w.Write([]byte(vtf.Name))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.Field(i)))
|
||||
if i < (numFields - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(d.w, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(d.w, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it in case any new
|
||||
// types are added.
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(d.w, "%v", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fdump is a helper function to consolidate the logic from the various public
|
||||
// methods which take varying writers and config states.
|
||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if arg == nil {
|
||||
w.Write(interfaceBytes)
|
||||
w.Write(spaceBytes)
|
||||
w.Write(nilAngleBytes)
|
||||
w.Write(newlineBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(&Config, w, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(&Config, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by an exported package global,
|
||||
spew.Config. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func Dump(a ...interface{}) {
|
||||
fdump(&Config, os.Stdout, a...)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user