diff --git a/go.mod b/go.mod index a01d0403f5ae..d15eeb1bdf0f 100644 --- a/go.mod +++ b/go.mod @@ -8,17 +8,18 @@ require ( github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/emicklei/go-restful v2.9.6+incompatible - github.com/evanphx/json-patch v4.1.0+incompatible // indirect + github.com/evanphx/json-patch v4.2.0+incompatible // indirect github.com/gogo/protobuf v1.2.0 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect + github.com/google/gofuzz v1.0.0 // indirect github.com/googleapis/gnostic v0.2.0 // indirect github.com/gorilla/websocket v1.4.0 // indirect github.com/igm/sockjs-go v2.0.1+incompatible // indirect github.com/imdario/mergo v0.3.6 // indirect - github.com/json-iterator/go v1.1.5 // indirect + github.com/json-iterator/go v1.1.6 // indirect + github.com/kr/pretty v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/gomega v1.5.0 // indirect github.com/opencontainers/go-digest v1.0.0-rc1 // indirect @@ -26,22 +27,28 @@ require ( github.com/prometheus/common v0.0.0-20181218105931-67670fe90761 // indirect github.com/prometheus/procfs v0.0.0-20190102135031-14fa7590c24d // indirect github.com/spf13/pflag v1.0.3 - golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc // indirect - golang.org/x/net v0.0.0-20181220203305-927f97764cc3 - golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 // indirect - golang.org/x/sys v0.0.0-20190102155601-82a175fd1598 // indirect - golang.org/x/text v0.3.0 + golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 // indirect + golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect + golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect + golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect + golang.org/x/text v0.3.2 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect - google.golang.org/appengine v1.4.0 // indirect + google.golang.org/appengine v1.5.0 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/igm/sockjs-go.v2 v2.0.0 gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2 - k8s.io/api v0.0.0-20190313235455-40a48860b5ab - k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1 + k8s.io/api v0.0.0-20190627205229-acea843d18eb + k8s.io/apiextensions-apiserver v0.0.0-20190315093550-53c4693659ed + k8s.io/apimachinery v0.0.0-20190627205106-bc5732d141a8 k8s.io/client-go v11.0.0+incompatible k8s.io/heapster v1.5.4 - k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be // indirect + k8s.io/klog v0.3.1 // indirect + k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 // indirect k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a // indirect sigs.k8s.io/yaml v1.1.0 // indirect ) + +replace k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1 diff --git a/go.sum b/go.sum index 97991281588e..d8ecb386d0df 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,18 +14,19 @@ github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f h1:AUj1VoZUfhP github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w= github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc= -github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= @@ -35,14 +37,19 @@ github.com/igm/sockjs-go v2.0.1+incompatible h1:iyv0auU1Xh1KC8N+GIiLPa3zZXwRsfRZ github.com/igm/sockjs-go v2.0.1+incompatible/go.mod h1:Yu6pvqjNniWNJe07LPObeCG6R77Qc97C6Kss0roF8tU= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -69,29 +76,45 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M= -golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190102155601-82a175fd1598 h1:S8GOgffXV1X3fpVG442QRfWOt0iFl79eHJ7OPt725bo= -golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/igm/sockjs-go.v2 v2.0.0 h1:NfDyi1jrF9v2VOPESefhKH1NRqpoE9tp4v6kxVR3ubs= @@ -105,8 +128,10 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -k8s.io/api v0.0.0-20190313235455-40a48860b5ab h1:DG9A67baNpoeweOy2spF1OWHhnVY5KR7/Ek/+U1lVZc= -k8s.io/api v0.0.0-20190313235455-40a48860b5ab/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190627205229-acea843d18eb h1:GPqcjfATaKQE9eB8VRN0cpueOB7NQZ7ZuAGy9j2fZoo= +k8s.io/api v0.0.0-20190627205229-acea843d18eb/go.mod h1:dNIey7Yoxc4u51YMhX4E5Cs6xiuGvXIGghzAZ9RzR88= +k8s.io/apiextensions-apiserver v0.0.0-20190315093550-53c4693659ed h1:rCteec//ELIjZMfjIGQbVtZooyaofqDJwsmWwWKItNs= +k8s.io/apiextensions-apiserver v0.0.0-20190315093550-53c4693659ed/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1 h1:IS7K02iBkQXpCeieSiyJjGoLSdVOv2DbPaWHJ+ZtgKg= k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= @@ -115,8 +140,10 @@ k8s.io/heapster v1.5.4 h1:lH2GCZdqRmUKDoyqRgiXbRmIcevaPYTvkguOuYUl8gQ= k8s.io/heapster v1.5.4/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM= k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be h1:aWEq4nbj7HRJ0mtKYjNSk/7X28Tl6TI6FeG8gKF+r7Q= -k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= +k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw= k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= diff --git a/src/app/backend/api/types.go b/src/app/backend/api/types.go index 676218f81cc9..95ee8c8bd6cf 100644 --- a/src/app/backend/api/types.go +++ b/src/app/backend/api/types.go @@ -110,29 +110,30 @@ type ResourceKind string // List of all resource kinds supported by the UI. const ( - ResourceKindConfigMap = "configmap" - ResourceKindDaemonSet = "daemonset" - ResourceKindDeployment = "deployment" - ResourceKindEvent = "event" - ResourceKindHorizontalPodAutoscaler = "horizontalpodautoscaler" - ResourceKindIngress = "ingress" - ResourceKindJob = "job" - ResourceKindCronJob = "cronjob" - ResourceKindLimitRange = "limitrange" - ResourceKindNamespace = "namespace" - ResourceKindNode = "node" - ResourceKindPersistentVolumeClaim = "persistentvolumeclaim" - ResourceKindPersistentVolume = "persistentvolume" - ResourceKindPod = "pod" - ResourceKindReplicaSet = "replicaset" - ResourceKindReplicationController = "replicationcontroller" - ResourceKindResourceQuota = "resourcequota" - ResourceKindSecret = "secret" - ResourceKindService = "service" - ResourceKindStatefulSet = "statefulset" - ResourceKindStorageClass = "storageclass" - ResourceKindClusterRole = "clusterrole" - ResourceKindEndpoint = "endpoint" + ResourceKindConfigMap = "configmap" + ResourceKindDaemonSet = "daemonset" + ResourceKindDeployment = "deployment" + ResourceKindEvent = "event" + ResourceKindHorizontalPodAutoscaler = "horizontalpodautoscaler" + ResourceKindIngress = "ingress" + ResourceKindJob = "job" + ResourceKindCronJob = "cronjob" + ResourceKindLimitRange = "limitrange" + ResourceKindNamespace = "namespace" + ResourceKindNode = "node" + ResourceKindPersistentVolumeClaim = "persistentvolumeclaim" + ResourceKindPersistentVolume = "persistentvolume" + ResourceKindCustomResourceDefinition = "customresourcedefinition" + ResourceKindPod = "pod" + ResourceKindReplicaSet = "replicaset" + ResourceKindReplicationController = "replicationcontroller" + ResourceKindResourceQuota = "resourcequota" + ResourceKindSecret = "secret" + ResourceKindService = "service" + ResourceKindStatefulSet = "statefulset" + ResourceKindStorageClass = "storageclass" + ResourceKindClusterRole = "clusterrole" + ResourceKindEndpoint = "endpoint" ) // ClientType represents type of client that is used to perform generic operations on resources. @@ -142,14 +143,15 @@ type ClientType string // List of client types supported by the UI. const ( - ClientTypeDefault = "restclient" - ClientTypeExtensionClient = "extensionclient" - ClientTypeAppsClient = "appsclient" - ClientTypeBatchClient = "batchclient" - ClientTypeBetaBatchClient = "betabatchclient" - ClientTypeAutoscalingClient = "autoscalingclient" - ClientTypeStorageClient = "storageclient" - ClientTypeRbacClient = "rbacclient" + ClientTypeDefault = "restclient" + ClientTypeExtensionClient = "extensionclient" + ClientTypeAppsClient = "appsclient" + ClientTypeBatchClient = "batchclient" + ClientTypeBetaBatchClient = "betabatchclient" + ClientTypeAutoscalingClient = "autoscalingclient" + ClientTypeStorageClient = "storageclient" + ClientTypeRbacClient = "rbacclient" + ClientTypeAPIExtensionsClient = "apiextensionsclient" ) // Mapping from resource kind to K8s apiserver API path. This is mostly pluralization, because @@ -163,29 +165,30 @@ var KindToAPIMapping = map[string]struct { // Is this object global scoped (not below a namespace). Namespaced bool }{ - ResourceKindConfigMap: {"configmaps", ClientTypeDefault, true}, - ResourceKindDaemonSet: {"daemonsets", ClientTypeExtensionClient, true}, - ResourceKindDeployment: {"deployments", ClientTypeExtensionClient, true}, - ResourceKindEvent: {"events", ClientTypeDefault, true}, - ResourceKindHorizontalPodAutoscaler: {"horizontalpodautoscalers", ClientTypeAutoscalingClient, true}, - ResourceKindIngress: {"ingresses", ClientTypeExtensionClient, true}, - ResourceKindJob: {"jobs", ClientTypeBatchClient, true}, - ResourceKindCronJob: {"cronjobs", ClientTypeBetaBatchClient, true}, - ResourceKindLimitRange: {"limitrange", ClientTypeDefault, true}, - ResourceKindNamespace: {"namespaces", ClientTypeDefault, false}, - ResourceKindNode: {"nodes", ClientTypeDefault, false}, - ResourceKindPersistentVolumeClaim: {"persistentvolumeclaims", ClientTypeDefault, true}, - ResourceKindPersistentVolume: {"persistentvolumes", ClientTypeDefault, false}, - ResourceKindPod: {"pods", ClientTypeDefault, true}, - ResourceKindReplicaSet: {"replicasets", ClientTypeExtensionClient, true}, - ResourceKindReplicationController: {"replicationcontrollers", ClientTypeDefault, true}, - ResourceKindResourceQuota: {"resourcequotas", ClientTypeDefault, true}, - ResourceKindSecret: {"secrets", ClientTypeDefault, true}, - ResourceKindService: {"services", ClientTypeDefault, true}, - ResourceKindStatefulSet: {"statefulsets", ClientTypeAppsClient, true}, - ResourceKindStorageClass: {"storageclasses", ClientTypeStorageClient, false}, - ResourceKindEndpoint: {"endpoints", ClientTypeDefault, true}, - ResourceKindClusterRole: {"clusterroles", ClientTypeRbacClient, false}, + ResourceKindConfigMap: {"configmaps", ClientTypeDefault, true}, + ResourceKindDaemonSet: {"daemonsets", ClientTypeExtensionClient, true}, + ResourceKindDeployment: {"deployments", ClientTypeExtensionClient, true}, + ResourceKindEvent: {"events", ClientTypeDefault, true}, + ResourceKindHorizontalPodAutoscaler: {"horizontalpodautoscalers", ClientTypeAutoscalingClient, true}, + ResourceKindIngress: {"ingresses", ClientTypeExtensionClient, true}, + ResourceKindJob: {"jobs", ClientTypeBatchClient, true}, + ResourceKindCronJob: {"cronjobs", ClientTypeBetaBatchClient, true}, + ResourceKindLimitRange: {"limitrange", ClientTypeDefault, true}, + ResourceKindNamespace: {"namespaces", ClientTypeDefault, false}, + ResourceKindNode: {"nodes", ClientTypeDefault, false}, + ResourceKindPersistentVolumeClaim: {"persistentvolumeclaims", ClientTypeDefault, true}, + ResourceKindPersistentVolume: {"persistentvolumes", ClientTypeDefault, false}, + ResourceKindCustomResourceDefinition: {"customresourcedefinitions", ClientTypeAPIExtensionsClient, false}, + ResourceKindPod: {"pods", ClientTypeDefault, true}, + ResourceKindReplicaSet: {"replicasets", ClientTypeExtensionClient, true}, + ResourceKindReplicationController: {"replicationcontrollers", ClientTypeDefault, true}, + ResourceKindResourceQuota: {"resourcequotas", ClientTypeDefault, true}, + ResourceKindSecret: {"secrets", ClientTypeDefault, true}, + ResourceKindService: {"services", ClientTypeDefault, true}, + ResourceKindStatefulSet: {"statefulsets", ClientTypeAppsClient, true}, + ResourceKindStorageClass: {"storageclasses", ClientTypeStorageClient, false}, + ResourceKindEndpoint: {"endpoints", ClientTypeDefault, true}, + ResourceKindClusterRole: {"clusterroles", ClientTypeRbacClient, false}, } // IsSelectorMatching returns true when an object with the given selector targets the same diff --git a/src/app/backend/auth/manager_test.go b/src/app/backend/auth/manager_test.go index f6b8d7f57830..ba4095ace79c 100644 --- a/src/app/backend/auth/manager_test.go +++ b/src/app/backend/auth/manager_test.go @@ -27,6 +27,7 @@ import ( "github.com/kubernetes/dashboard/src/app/backend/errors" v1 "k8s.io/api/authorization/v1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -46,10 +47,18 @@ func (self *fakeClientManager) Client(req *restful.Request) (kubernetes.Interfac return nil, nil } +func (self *fakeClientManager) APIExtensionsClient(req *restful.Request) (apiextensionsclientset.Interface, error) { + return nil, nil +} + func (self *fakeClientManager) InsecureClient() kubernetes.Interface { return nil } +func (self *fakeClientManager) InsecureAPIExtensionsClient() apiextensionsclientset.Interface { + return nil +} + func (self *fakeClientManager) SetTokenManager(manager authApi.TokenManager) {} func (self *fakeClientManager) Config(req *restful.Request) (*rest.Config, error) { @@ -70,7 +79,7 @@ func (self *fakeClientManager) HasAccess(authInfo api.AuthInfo) error { func (self *fakeClientManager) VerberClient(req *restful.Request) (clientapi.ResourceVerber, error) { return client.NewResourceVerber(nil, nil, nil, nil, nil, - nil, nil, nil), nil + nil, nil, nil, nil), nil } func (self *fakeClientManager) CanI(req *restful.Request, ssar *v1.SelfSubjectAccessReview) bool { diff --git a/src/app/backend/client/api/types.go b/src/app/backend/client/api/types.go index 9ec88e27f143..f16bf48e469e 100644 --- a/src/app/backend/client/api/types.go +++ b/src/app/backend/client/api/types.go @@ -15,9 +15,10 @@ package api import ( - restful "github.com/emicklei/go-restful" + "github.com/emicklei/go-restful" authApi "github.com/kubernetes/dashboard/src/app/backend/auth/api" v1 "k8s.io/api/authorization/v1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -38,6 +39,8 @@ const ( type ClientManager interface { Client(req *restful.Request) (kubernetes.Interface, error) InsecureClient() kubernetes.Interface + APIExtensionsClient(req *restful.Request) (apiextensionsclientset.Interface, error) + InsecureAPIExtensionsClient() apiextensionsclientset.Interface CanI(req *restful.Request, ssar *v1.SelfSubjectAccessReview) bool Config(req *restful.Request) (*rest.Config, error) ClientCmdConfig(req *restful.Request) (clientcmd.ClientConfig, error) diff --git a/src/app/backend/client/manager.go b/src/app/backend/client/manager.go index cd7f5a74a314..0fcfc0758c4b 100644 --- a/src/app/backend/client/manager.go +++ b/src/app/backend/client/manager.go @@ -18,8 +18,9 @@ import ( "log" "strings" - restful "github.com/emicklei/go-restful" + "github.com/emicklei/go-restful" v1 "k8s.io/api/authorization/v1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -67,6 +68,9 @@ type clientManager struct { inClusterConfig *rest.Config // Responsible for decrypting tokens coming in request header. Used for authentication. tokenManager authApi.TokenManager + // API Extensions client created without providing auth info. It uses permissions granted to + // service account used by dashboard or kubeconfig file if it was passed during dashboard init. + insecureAPIExtensionsClient apiextensionsclientset.Interface // Kubernetes client created without providing auth info. It uses permissions granted to // service account used by dashboard or kubeconfig file if it was passed during dashboard init. insecureClient kubernetes.Interface @@ -91,6 +95,21 @@ func (self *clientManager) Client(req *restful.Request) (kubernetes.Interface, e return self.InsecureClient(), nil } +// APIExtensionsClient returns an API Extensions client. In case dashboard login is enabled and +// option to skip login page is disabled only secure client will be returned, otherwise insecure +// client will be used. +func (self *clientManager) APIExtensionsClient(req *restful.Request) (apiextensionsclientset.Interface, error) { + if req == nil { + return nil, errors.NewBadRequest("request can not be nil!") + } + + if self.isSecureModeEnabled(req) { + return self.secureAPIExtensionsClient(req) + } + + return self.InsecureAPIExtensionsClient(), nil +} + // Config returns a rest config. In case dashboard login is enabled and option to skip // login page is disabled only secure config will be returned, otherwise insecure config will be // used. @@ -113,6 +132,13 @@ func (self *clientManager) InsecureClient() kubernetes.Interface { return self.insecureClient } +// InsecureAPIExtensionsClient returns API Extensions client that was created without providing +// auth info. It uses permissions granted to service account used by dashboard or kubeconfig file +// if it was passed during dashboard init. +func (self *clientManager) InsecureAPIExtensionsClient() apiextensionsclientset.Interface { + return self.insecureAPIExtensionsClient +} + // InsecureConfig returns kubernetes client config that used privileges of dashboard service account // or kubeconfig file if it was passed during dashboard init. func (self *clientManager) InsecureConfig() *rest.Config { @@ -188,15 +214,20 @@ func (self *clientManager) HasAccess(authInfo api.AuthInfo) error { // VerberClient returns new verber client based on authentication information extracted from request func (self *clientManager) VerberClient(req *restful.Request) (clientapi.ResourceVerber, error) { - client, err := self.Client(req) + k8sClient, err := self.Client(req) + if err != nil { + return nil, err + } + + apiextensionsclient, err := self.APIExtensionsClient(req) if err != nil { return nil, err } - return NewResourceVerber(client.CoreV1().RESTClient(), - client.ExtensionsV1beta1().RESTClient(), client.AppsV1().RESTClient(), - client.BatchV1().RESTClient(), client.BatchV1beta1().RESTClient(), client.AutoscalingV1().RESTClient(), - client.StorageV1().RESTClient(), client.RbacV1().RESTClient()), nil + return NewResourceVerber(k8sClient.CoreV1().RESTClient(), + k8sClient.ExtensionsV1beta1().RESTClient(), k8sClient.AppsV1().RESTClient(), + k8sClient.BatchV1().RESTClient(), k8sClient.BatchV1beta1().RESTClient(), k8sClient.AutoscalingV1().RESTClient(), + k8sClient.StorageV1().RESTClient(), k8sClient.RbacV1().RESTClient(), apiextensionsclient.ApiextensionsV1beta1().RESTClient()), nil } // SetTokenManager sets the token manager that will be used for token decryption. @@ -313,6 +344,20 @@ func (self *clientManager) secureClient(req *restful.Request) (kubernetes.Interf return client, nil } +func (self *clientManager) secureAPIExtensionsClient(req *restful.Request) (apiextensionsclientset.Interface, error) { + cfg, err := self.secureConfig(req) + if err != nil { + return nil, err + } + + client, err := apiextensionsclientset.NewForConfig(cfg) + if err != nil { + return nil, err + } + + return client, nil +} + func (self *clientManager) secureConfig(req *restful.Request) (*rest.Config, error) { cmdConfig, err := self.ClientCmdConfig(req) if err != nil { @@ -331,7 +376,7 @@ func (self *clientManager) secureConfig(req *restful.Request) (*rest.Config, err // Initializes client manager func (self *clientManager) init() { self.initInClusterConfig() - self.initInsecureClient() + self.initInsecureClients() self.initCSRFKey() } @@ -367,14 +412,21 @@ func (self *clientManager) initCSRFKey() { self.csrfKey = csrf.NewCsrfTokenManager(self.insecureClient).Token() } -func (self *clientManager) initInsecureClient() { +// Initializes Kubernetes client and API extensions client. +func (self *clientManager) initInsecureClients() { self.initInsecureConfig() - client, err := kubernetes.NewForConfig(self.insecureConfig) + k8sClient, err := kubernetes.NewForConfig(self.insecureConfig) + if err != nil { + panic(err) + } + + apiextensionsclient, err := apiextensionsclientset.NewForConfig(self.insecureConfig) if err != nil { panic(err) } - self.insecureClient = client + self.insecureClient = k8sClient + self.insecureAPIExtensionsClient = apiextensionsclient } func (self *clientManager) initInsecureConfig() { diff --git a/src/app/backend/client/manager_test.go b/src/app/backend/client/manager_test.go index 3238870f04d4..03f00322d212 100644 --- a/src/app/backend/client/manager_test.go +++ b/src/app/backend/client/manager_test.go @@ -116,6 +116,81 @@ func TestSecureClient(t *testing.T) { } } +func TestAPIExtensionsClient(t *testing.T) { + cases := []struct { + request *restful.Request + }{ + { + &restful.Request{ + Request: &http.Request{ + Header: http.Header(map[string][]string{}), + }, + }, + }, + } + + for _, c := range cases { + manager := NewClientManager("", "http://localhost:8080") + _, err := manager.APIExtensionsClient(c.request) + + if err != nil { + t.Fatalf("APIExtensionsClient(%v): Expected API extensions client to be created"+ + " but error was thrown: %s", c.request, err.Error()) + } + } +} + +func TestSecureAPIExtensionsClient(t *testing.T) { + cases := []struct { + request *restful.Request + expectedError bool + err error + }{ + { + request: &restful.Request{ + Request: &http.Request{ + Header: http.Header(map[string][]string{}), + TLS: &tls.ConnectionState{}, + }, + }, + expectedError: true, + err: errors.NewUnauthorized(errors.MsgLoginUnauthorizedError), + }, + { + request: &restful.Request{ + Request: &http.Request{ + Header: http.Header(map[string][]string{"Authorization": {"Bearer asd"}}), + TLS: &tls.ConnectionState{}, + }, + }, + expectedError: false, + err: nil, + }, + } + + for _, c := range cases { + manager := NewClientManager("", "http://localhost:8080") + _, err := manager.APIExtensionsClient(c.request) + + if err == nil && !c.expectedError { + continue + } + + if !c.expectedError && err != nil { + t.Fatalf("APIExtensions(%v): Expected client to be created but error"+ + " was thrown: %s", c.request, err.Error()) + } + + if c.expectedError && err == nil { + t.Fatalf("Expected error %v but got nil", c.err) + } + + if c.err.Error() != err.Error() { + t.Fatalf("Expected error %v but got %v", c.err, err) + } + } +} + func TestCSRFKey(t *testing.T) { manager := NewClientManager("", "http://localhost:8080") key := manager.CSRFKey() @@ -217,9 +292,16 @@ func TestVerberClient(t *testing.T) { } } -func TestClientManager_InsecureClient(t *testing.T) { +func TestClientManager_InsecureClients(t *testing.T) { manager := NewClientManager("", "http://localhost:8080") if manager.InsecureClient() == nil { t.Fatalf("InsecureClient(): Expected insecure client not to be nil") } } + +func TestClientManager_InsecureAPIExtensionsClient(t *testing.T) { + manager := NewClientManager("", "http://localhost:8080") + if manager.InsecureAPIExtensionsClient() == nil { + t.Fatalf("InsecureClient(): Expected insecure client not to be nil") + } +} diff --git a/src/app/backend/client/verber.go b/src/app/backend/client/verber.go index 48591dc58f45..1ded3c523f48 100644 --- a/src/app/backend/client/verber.go +++ b/src/app/backend/client/verber.go @@ -29,14 +29,15 @@ import ( // resourceVerber is a struct responsible for doing common verb operations on resources, like // DELETE, PUT, UPDATE. type resourceVerber struct { - client RESTClient - extensionsClient RESTClient - appsClient RESTClient - batchClient RESTClient - betaBatchClient RESTClient - autoscalingClient RESTClient - storageClient RESTClient - rbacClient RESTClient + client RESTClient + extensionsClient RESTClient + appsClient RESTClient + batchClient RESTClient + betaBatchClient RESTClient + autoscalingClient RESTClient + storageClient RESTClient + rbacClient RESTClient + apiExtensionsClient RESTClient } func (verber *resourceVerber) getRESTClientByType(clientType api.ClientType) RESTClient { @@ -55,6 +56,8 @@ func (verber *resourceVerber) getRESTClientByType(clientType api.ClientType) RES return verber.storageClient case api.ClientTypeRbacClient: return verber.rbacClient + case api.ClientTypeAPIExtensionsClient: + return verber.apiExtensionsClient default: return verber.client } @@ -70,9 +73,9 @@ type RESTClient interface { // NewResourceVerber creates a new resource verber that uses the given client for performing operations. func NewResourceVerber(client, extensionsClient, appsClient, batchClient, betaBatchClient, autoscalingClient, storageClient, - rbacClient RESTClient) clientapi.ResourceVerber { + rbacClient, apiExtensionsClient RESTClient) clientapi.ResourceVerber { return &resourceVerber{client, extensionsClient, appsClient, - batchClient, betaBatchClient, autoscalingClient, storageClient, rbacClient} + batchClient, betaBatchClient, autoscalingClient, storageClient, rbacClient, apiExtensionsClient} } // Delete deletes the resource of the given kind in the given namespace with the given name. diff --git a/src/app/backend/handler/apihandler.go b/src/app/backend/handler/apihandler.go index ccfab94fa36e..8d61fbc62ddf 100644 --- a/src/app/backend/handler/apihandler.go +++ b/src/app/backend/handler/apihandler.go @@ -21,10 +21,6 @@ import ( "strings" restful "github.com/emicklei/go-restful" - "golang.org/x/net/xsrftoken" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/remotecommand" - "github.com/kubernetes/dashboard/src/app/backend/api" "github.com/kubernetes/dashboard/src/app/backend/auth" authApi "github.com/kubernetes/dashboard/src/app/backend/auth/api" @@ -38,6 +34,7 @@ import ( "github.com/kubernetes/dashboard/src/app/backend/resource/container" "github.com/kubernetes/dashboard/src/app/backend/resource/controller" "github.com/kubernetes/dashboard/src/app/backend/resource/cronjob" + "github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition" "github.com/kubernetes/dashboard/src/app/backend/resource/daemonset" "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" "github.com/kubernetes/dashboard/src/app/backend/resource/deployment" @@ -62,6 +59,9 @@ import ( settingsApi "github.com/kubernetes/dashboard/src/app/backend/settings/api" "github.com/kubernetes/dashboard/src/app/backend/systembanner" "github.com/kubernetes/dashboard/src/app/backend/validation" + "golang.org/x/net/xsrftoken" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/remotecommand" ) const ( @@ -527,6 +527,31 @@ func CreateHTTPAPIHandler(iManager integration.IntegrationManager, cManager clie To(apiHandler.handleGetPersistentVolumeClaimDetail). Writes(persistentvolumeclaim.PersistentVolumeClaimDetail{})) + apiV1Ws.Route( + apiV1Ws.GET("/crd"). + To(apiHandler.handleGetCustomResourceDefinitionList). + Writes(customresourcedefinition.CustomResourceDefinitionList{})) + + apiV1Ws.Route( + apiV1Ws.GET("/crd/{crd}"). + To(apiHandler.handleGetCustomResourceDefinitionDetail). + Writes(customresourcedefinition.CustomResourceDefinitionDetail{})) + + apiV1Ws.Route( + apiV1Ws.GET("/crd/{namespace}/{crd}/object"). + To(apiHandler.handleGetCustomResourceObjectList). + Writes(customresourcedefinition.CustomResourceObjectList{})) + + apiV1Ws.Route( + apiV1Ws.GET("/crd/{namespace}/{crd}/object/{name}"). + To(apiHandler.handleGetCustomResourceObjectDetail). + Writes(customresourcedefinition.CustomResourceObjectDetail{})) + + apiV1Ws.Route( + apiV1Ws.GET("/crd/{namespace}/{crd}/object/{name}/event"). + To(apiHandler.handleGetCustomResourceObjectEvents). + Writes(common.EventList{})) + apiV1Ws.Route( apiV1Ws.GET("/storageclass"). To(apiHandler.handleGetStorageClassList). @@ -2044,6 +2069,118 @@ func (apiHandler *APIHandler) handleGetPodPersistentVolumeClaims(request *restfu response.WriteHeaderAndEntity(http.StatusOK, result) } +func (apiHandler *APIHandler) handleGetCustomResourceDefinitionList(request *restful.Request, response *restful.Response) { + apiextensionsclient, err := apiHandler.cManager.APIExtensionsClient(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + dataSelect := parseDataSelectPathParameter(request) + result, err := customresourcedefinition.GetCustomResourceDefinitionList(apiextensionsclient, dataSelect) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + response.WriteHeaderAndEntity(http.StatusOK, result) +} + +func (apiHandler *APIHandler) handleGetCustomResourceDefinitionDetail(request *restful.Request, response *restful.Response) { + config, err := apiHandler.cManager.Config(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + apiextensionsclient, err := apiHandler.cManager.APIExtensionsClient(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + name := request.PathParameter("crd") + result, err := customresourcedefinition.GetCustomResourceDefinitionDetail(apiextensionsclient, config, name) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + response.WriteHeaderAndEntity(http.StatusOK, result) +} + +func (apiHandler *APIHandler) handleGetCustomResourceObjectList(request *restful.Request, response *restful.Response) { + config, err := apiHandler.cManager.Config(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + apiextensionsclient, err := apiHandler.cManager.APIExtensionsClient(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + crdName := request.PathParameter("crd") + namespace := parseNamespacePathParameter(request) + dataSelect := parseDataSelectPathParameter(request) + result, err := customresourcedefinition.GetCustomResourceObjectList(apiextensionsclient, config, namespace, dataSelect, crdName) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + response.WriteHeaderAndEntity(http.StatusOK, result) +} + +func (apiHandler *APIHandler) handleGetCustomResourceObjectDetail(request *restful.Request, response *restful.Response) { + config, err := apiHandler.cManager.Config(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + apiextensionsclient, err := apiHandler.cManager.APIExtensionsClient(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + name := request.PathParameter("name") + crdName := request.PathParameter("crd") + namespace := parseNamespacePathParameter(request) + result, err := customresourcedefinition.GetCustomResourceObjectDetail(apiextensionsclient, namespace, config, crdName, name) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + response.WriteHeaderAndEntity(http.StatusOK, result) +} + +func (apiHandler *APIHandler) handleGetCustomResourceObjectEvents(request *restful.Request, response *restful.Response) { + log.Println("Getting events related to a custom resource object in namespace") + + k8sClient, err := apiHandler.cManager.Client(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + namespace := request.PathParameter("namespace") + name := request.PathParameter("name") + dataSelect := parseDataSelectPathParameter(request) + dataSelect.MetricQuery = dataselect.StandardMetrics + result, err := customresourcedefinition.GetEventsForCustomResourceObject(k8sClient, dataSelect, namespace, name) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + response.WriteHeaderAndEntity(http.StatusOK, result) +} + func (apiHandler *APIHandler) handleLogSource(request *restful.Request, response *restful.Response) { k8sClient, err := apiHandler.cManager.Client(request) if err != nil { diff --git a/src/app/backend/resource/common/resourcechannels.go b/src/app/backend/resource/common/resourcechannels.go index d66133a0097e..94b7fa79e8ab 100644 --- a/src/app/backend/resource/common/resourcechannels.go +++ b/src/app/backend/resource/common/resourcechannels.go @@ -24,6 +24,8 @@ import ( extensions "k8s.io/api/extensions/v1beta1" rbac "k8s.io/api/rbac/v1" storage "k8s.io/api/storage/v1" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" ) @@ -834,6 +836,31 @@ func GetPersistentVolumeClaimListChannel(client client.Interface, nsQuery *Names return channel } +// CustomResourceDefinitionChannel is a list and error channels to CustomResourceDefinition. +type CustomResourceDefinitionChannel struct { + List chan *apiextensions.CustomResourceDefinitionList + Error chan error +} + +// GetCustomResourceDefinitionChannel returns a pair of channels to a CustomResourceDefinition list and errors +// that both must be read numReads times. +func GetCustomResourceDefinitionChannel(client apiextensionsclientset.Interface, numReads int) CustomResourceDefinitionChannel { + channel := CustomResourceDefinitionChannel{ + List: make(chan *apiextensions.CustomResourceDefinitionList, numReads), + Error: make(chan error, numReads), + } + + go func() { + list, err := client.ApiextensionsV1beta1().CustomResourceDefinitions().List(api.ListEverything) + for i := 0; i < numReads; i++ { + channel.List <- list + channel.Error <- err + } + }() + + return channel +} + // ResourceQuotaListChannel is a list and error channels to ResourceQuotas. type ResourceQuotaListChannel struct { List chan *v1.ResourceQuotaList diff --git a/src/app/backend/resource/customresourcedefinition/client.go b/src/app/backend/resource/customresourcedefinition/client.go new file mode 100644 index 000000000000..824ec6513273 --- /dev/null +++ b/src/app/backend/resource/customresourcedefinition/client.go @@ -0,0 +1,53 @@ +// 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 customresourcedefinition + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/rest" +) + +func newRESTClient(config *rest.Config, groupVersion schema.GroupVersion) (*rest.RESTClient, error) { + scheme := runtime.NewScheme() + schemeBuilder := runtime.NewSchemeBuilder( + func(scheme *runtime.Scheme) error { + scheme.AddKnownTypes( + groupVersion, + &metav1.ListOptions{}, + &metav1.DeleteOptions{}, + &CustomResourceObject{}, + &CustomResourceObjectList{}, + ) + return nil + }) + if err := schemeBuilder.AddToScheme(scheme); err != nil { + return nil, err + } + + config.GroupVersion = &groupVersion + config.APIPath = "/apis" + config.ContentType = runtime.ContentTypeJSON + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)} + + client, err := rest.RESTClientFor(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/src/app/backend/resource/customresourcedefinition/common.go b/src/app/backend/resource/customresourcedefinition/common.go new file mode 100644 index 000000000000..8b8f68c88f5d --- /dev/null +++ b/src/app/backend/resource/customresourcedefinition/common.go @@ -0,0 +1,105 @@ +// 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 customresourcedefinition + +import ( + "strings" + + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type CustomResourceDefinitionCell apiextensions.CustomResourceDefinition + +func (self CustomResourceDefinitionCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue { + switch name { + case dataselect.NameProperty: + return dataselect.StdComparableString(self.ObjectMeta.Name) + case dataselect.CreationTimestampProperty: + return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time) + default: + // if name is not supported then just return a constant dummy value, sort will have no effect. + return nil + } +} + +func toCells(std []apiextensions.CustomResourceDefinition) []dataselect.DataCell { + cells := make([]dataselect.DataCell, len(std)) + for i := range std { + cells[i] = CustomResourceDefinitionCell(std[i]) + } + + return cells +} + +func fromCells(cells []dataselect.DataCell) []apiextensions.CustomResourceDefinition { + std := make([]apiextensions.CustomResourceDefinition, len(cells)) + for i := range std { + std[i] = apiextensions.CustomResourceDefinition(cells[i].(CustomResourceDefinitionCell)) + } + + return std +} + +// The code below allows to perform complex data section on ThirdPartyResourceObject. +type CustomResourceObjectCell CustomResourceObject + +func (self CustomResourceObjectCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue { + switch name { + case dataselect.NameProperty: + return dataselect.StdComparableString(self.Metadata.Name) + case dataselect.CreationTimestampProperty: + return dataselect.StdComparableTime(self.Metadata.CreationTimestamp.Time) + case dataselect.NamespaceProperty: + return dataselect.StdComparableString(self.Metadata.Namespace) + default: + // if name is not supported then just return a constant dummy value, sort will have no effect. + return nil + } +} + +func toObjectCells(std []CustomResourceObject) []dataselect.DataCell { + cells := make([]dataselect.DataCell, len(std)) + for i := range std { + cells[i] = CustomResourceObjectCell(std[i]) + } + return cells +} + +func fromObjectCells(cells []dataselect.DataCell) []CustomResourceObject { + std := make([]CustomResourceObject, len(cells)) + for i := range std { + std[i] = CustomResourceObject(cells[i].(CustomResourceObjectCell)) + } + return std +} + +// getCustomResourceDefinitionGroupVersion returns first group version of custom resource definition. +// It's also known as preferredVersion. +func getCustomResourceDefinitionGroupVersion(crd *apiextensions.CustomResourceDefinition) schema.GroupVersion { + version := crd.Spec.Version + group := "" + if strings.Contains(crd.ObjectMeta.Name, ".") { + group = crd.ObjectMeta.Name[strings.Index(crd.ObjectMeta.Name, ".")+1:] + } else { + group = crd.ObjectMeta.Name + } + + return schema.GroupVersion{ + Group: group, + Version: version, + } +} diff --git a/src/app/backend/resource/customresourcedefinition/detail.go b/src/app/backend/resource/customresourcedefinition/detail.go new file mode 100644 index 000000000000..da0f14157ec6 --- /dev/null +++ b/src/app/backend/resource/customresourcedefinition/detail.go @@ -0,0 +1,65 @@ +// 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 customresourcedefinition + +import ( + "github.com/kubernetes/dashboard/src/app/backend/api" + "github.com/kubernetes/dashboard/src/app/backend/resource/common" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" +) + +type CustomResourceDefinitionDetail struct { + CustomResourceDefinition `json:",inline"` +} + +// CustomResourceDefinition represents a custom resource definition. +type CustomResourceDefinition struct { + ObjectMeta api.ObjectMeta `json:"objectMeta"` + TypeMeta api.TypeMeta `json:"typeMeta"` + Version string `json:"version"` + Objects CustomResourceObjectList `json:"objects"` +} + +// GetCustomResourceDefinitionDetail returns detailed information about a custom resource definition. +func GetCustomResourceDefinitionDetail(client apiextensionsclientset.Interface, config *rest.Config, name string) (*CustomResourceDefinitionDetail, error) { + customResourceDefinition, err := client.ApiextensionsV1beta1(). + CustomResourceDefinitions(). + Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + objects, err := GetCustomResourceObjectList(client, config, &common.NamespaceQuery{}, dataselect.DefaultDataSelect, name) + if err != nil { + return nil, err + } + + return toCustomResourceDefinitionDetail(customResourceDefinition, objects), nil +} + +func toCustomResourceDefinitionDetail(customResourceDefinition *apiextensions.CustomResourceDefinition, objects CustomResourceObjectList) *CustomResourceDefinitionDetail { + return &CustomResourceDefinitionDetail{ + CustomResourceDefinition{ + ObjectMeta: api.NewObjectMeta(customResourceDefinition.ObjectMeta), + TypeMeta: api.NewTypeMeta(api.ResourceKindCustomResourceDefinition), + Version: customResourceDefinition.Spec.Version, + Objects: objects, + }, + } +} diff --git a/src/app/backend/resource/customresourcedefinition/events.go b/src/app/backend/resource/customresourcedefinition/events.go new file mode 100644 index 000000000000..c307b40ddd90 --- /dev/null +++ b/src/app/backend/resource/customresourcedefinition/events.go @@ -0,0 +1,28 @@ +// 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 customresourcedefinition + +import ( + "github.com/kubernetes/dashboard/src/app/backend/resource/common" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + "github.com/kubernetes/dashboard/src/app/backend/resource/event" + client "k8s.io/client-go/kubernetes" +) + +// GetEventsForCustomResourceObject gets events that are associated with this CR object. +func GetEventsForCustomResourceObject(client client.Interface, dsQuery *dataselect.DataSelectQuery, + namespace, name string) (*common.EventList, error) { + return event.GetResourceEvents(client, dsQuery, namespace, name) +} diff --git a/src/app/backend/resource/customresourcedefinition/events_test.go b/src/app/backend/resource/customresourcedefinition/events_test.go new file mode 100644 index 000000000000..a266888cc3e6 --- /dev/null +++ b/src/app/backend/resource/customresourcedefinition/events_test.go @@ -0,0 +1,85 @@ +// 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 customresourcedefinition + +import ( + "reflect" + "testing" + + "github.com/kubernetes/dashboard/src/app/backend/api" + "github.com/kubernetes/dashboard/src/app/backend/resource/common" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + v1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/kubernetes/fake" +) + +func TestGetEventsForCustomResourceObject(t *testing.T) { + cases := []struct { + namespace, objectName string + eventList *v1.EventList + objectList *unstructured.UnstructuredList + expected *common.EventList + }{ + { + "ns-1", "example-foo", + &v1.EventList{Items: []v1.Event{ + { + Message: "test-message", + ObjectMeta: metaV1.ObjectMeta{ + Name: "ev-1", Namespace: "ns-1", + Labels: map[string]string{"app": "test"}, + }, + InvolvedObject: v1.ObjectReference{UID: "test-uid"}}, + }}, + &unstructured.UnstructuredList{Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": "samplecontroller.k8s.io/v1alpha1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "example-foo", + "namespace": "ns-1", + "uid": "test-uid", + }, + }, + }, + }}, + &common.EventList{ + ListMeta: api.ListMeta{TotalItems: 1}, + Events: []common.Event{{ + TypeMeta: api.TypeMeta{Kind: api.ResourceKindEvent}, + ObjectMeta: api.ObjectMeta{Name: "ev-1", Namespace: "ns-1", + Labels: map[string]string{"app": "test"}}, + Message: "test-message", + Type: v1.EventTypeNormal, + }}, + Errors: []error{}, + }, + }, + } + + for _, c := range cases { + fakeClient := fake.NewSimpleClientset(c.eventList, c.objectList) + + actual, _ := GetEventsForCustomResourceObject(fakeClient, dataselect.NoDataSelect, c.namespace, c.objectName) + + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("GetEventsForCustomResourceObject == \ngot %#v, \nexpected %#v", actual, + c.expected) + } + } +} diff --git a/src/app/backend/resource/customresourcedefinition/list.go b/src/app/backend/resource/customresourcedefinition/list.go new file mode 100644 index 000000000000..b2a186eaa3fc --- /dev/null +++ b/src/app/backend/resource/customresourcedefinition/list.go @@ -0,0 +1,75 @@ +// 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 customresourcedefinition + +import ( + "github.com/kubernetes/dashboard/src/app/backend/api" + "github.com/kubernetes/dashboard/src/app/backend/errors" + "github.com/kubernetes/dashboard/src/app/backend/resource/common" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" +) + +// CustomResourceDefinitionList contains a list of Custom Resource Definitions in the cluster. +type CustomResourceDefinitionList struct { + ListMeta api.ListMeta `json:"listMeta"` + + // Unordered list of custom resource definitions + Items []CustomResourceDefinition `json:"items"` + + // List of non-critical errors, that occurred during resource retrieval. + Errors []error `json:"errors"` +} + +// GetCustomResourceDefinitionList returns all the custom resource definitions in the cluster. +func GetCustomResourceDefinitionList(client apiextensionsclientset.Interface, dsQuery *dataselect.DataSelectQuery) (*CustomResourceDefinitionList, error) { + channel := common.GetCustomResourceDefinitionChannel(client, 1) + crdList := <-channel.List + err := <-channel.Error + + nonCriticalErrors, criticalError := errors.HandleError(err) + if criticalError != nil { + return nil, criticalError + } + + return toCustomResourceDefinitionList(crdList.Items, nonCriticalErrors, dsQuery), nil +} + +func toCustomResourceDefinitionList(crds []apiextensions.CustomResourceDefinition, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *CustomResourceDefinitionList { + crdList := &CustomResourceDefinitionList{ + Items: make([]CustomResourceDefinition, 0), + ListMeta: api.ListMeta{TotalItems: len(crds)}, + Errors: nonCriticalErrors, + } + + crdCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toCells(crds), dsQuery) + crds = fromCells(crdCells) + crdList.ListMeta = api.ListMeta{TotalItems: filteredTotal} + + for _, crd := range crds { + crdList.Items = append(crdList.Items, toCustomResourceDefinition(&crd)) + } + + return crdList +} + +func toCustomResourceDefinition(crd *apiextensions.CustomResourceDefinition) CustomResourceDefinition { + return CustomResourceDefinition{ + ObjectMeta: api.NewObjectMeta(crd.ObjectMeta), + TypeMeta: api.NewTypeMeta(api.ResourceKindCustomResourceDefinition), + Version: crd.Spec.Version, + } +} diff --git a/src/app/backend/resource/customresourcedefinition/list_test.go b/src/app/backend/resource/customresourcedefinition/list_test.go new file mode 100644 index 000000000000..30df965ce0fa --- /dev/null +++ b/src/app/backend/resource/customresourcedefinition/list_test.go @@ -0,0 +1,88 @@ +// 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 customresourcedefinition + +import ( + "reflect" + "testing" + + "github.com/kubernetes/dashboard/src/app/backend/api" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetCustomResourceDefinition(t *testing.T) { + cases := []struct { + expectedActions []string + crdList *apiextensions.CustomResourceDefinitionList + expected *CustomResourceDefinitionList + }{ + { + []string{"list"}, + &apiextensions.CustomResourceDefinitionList{ + Items: []apiextensions.CustomResourceDefinition{ + { + ObjectMeta: metaV1.ObjectMeta{Name: "foos.samplecontroller.k8s.io"}, + Spec: apiextensions.CustomResourceDefinitionSpec{ + Names: apiextensions.CustomResourceDefinitionNames{ + Kind: "Foo", + Plural: "foos", + }, + Version: "v1alpha1", + }, + }, + }, + }, + &CustomResourceDefinitionList{ + ListMeta: api.ListMeta{TotalItems: 1}, + Items: []CustomResourceDefinition{ + { + ObjectMeta: api.ObjectMeta{Name: "foos.samplecontroller.k8s.io"}, + TypeMeta: api.TypeMeta{Kind: api.ResourceKindCustomResourceDefinition}, + Version: "v1alpha1", + }, + }, + Errors: []error{}, + }, + }, + } + + for _, c := range cases { + fakeClient := fake.NewSimpleClientset(c.crdList) + + actual, _ := GetCustomResourceDefinitionList(fakeClient, dataselect.DefaultDataSelect) + + actions := fakeClient.Actions() + if len(actions) != len(c.expectedActions) { + t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions, + len(c.expectedActions), len(actions)) + continue + } + + for i, verb := range c.expectedActions { + if actions[i].GetVerb() != verb { + t.Errorf("Unexpected action: %+v, expected %s", + actions[i], verb) + } + } + + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("GetCustomResourceDefinitionList(client, nil) == \ngot: %#v, \nexpected %#v", + actual, c.expected) + } + } +} diff --git a/src/app/backend/resource/customresourcedefinition/objects.go b/src/app/backend/resource/customresourcedefinition/objects.go new file mode 100644 index 000000000000..828abec82781 --- /dev/null +++ b/src/app/backend/resource/customresourcedefinition/objects.go @@ -0,0 +1,146 @@ +// 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 customresourcedefinition + +import ( + "github.com/kubernetes/dashboard/src/app/backend/api" + "github.com/kubernetes/dashboard/src/app/backend/errors" + "github.com/kubernetes/dashboard/src/app/backend/resource/common" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" +) + +type CustomResourceObjectDetail struct { + CustomResourceObject `json:",inline"` +} + +// CustomResourceObject represents a custom resource object. +type CustomResourceObject struct { + metav1.TypeMeta `json:",inline"` + Metadata metav1.ObjectMeta `json:"metadata,omitempty"` +} + +func (in *CustomResourceObject) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +func (in *CustomResourceObject) DeepCopy() *CustomResourceObject { + if in == nil { + return nil + } + out := new(CustomResourceObject) + *out = *in + return out +} + +// CustomResourceObjectList represents crd objects in a namespace. +type CustomResourceObjectList struct { + ListMeta api.ListMeta `json:"listMeta"` + metav1.TypeMeta `json:",inline"` + + // Unordered list of custom resource definitions + Items []CustomResourceObject `json:"items"` + + // List of non-critical errors, that occurred during resource retrieval. + Errors []error `json:"errors"` +} + +func (in *CustomResourceObjectList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +func (in *CustomResourceObjectList) DeepCopy() *CustomResourceObjectList { + if in == nil { + return nil + } + out := new(CustomResourceObjectList) + *out = *in + return out +} + +// GetCustomResourceObjectList gets objects for a CR. +func GetCustomResourceObjectList(client apiextensionsclientset.Interface, config *rest.Config, namespace *common.NamespaceQuery, + dsQuery *dataselect.DataSelectQuery, crdName string) (CustomResourceObjectList, error) { + var list CustomResourceObjectList + + customResourceDefinition, err := client.ApiextensionsV1beta1(). + CustomResourceDefinitions(). + Get(crdName, metav1.GetOptions{}) + nonCriticalErrors, criticalError := errors.HandleError(err) + if criticalError != nil { + return list, criticalError + } + + restClient, err := newRESTClient(config, getCustomResourceDefinitionGroupVersion(customResourceDefinition)) + nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors) + if criticalError != nil { + return list, criticalError + } + + err = restClient.Get(). + Namespace(namespace.ToRequestParam()). + Resource(customResourceDefinition.Spec.Names.Plural). + Do().Into(&list) + nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors) + if criticalError != nil { + return list, criticalError + } + + // Return only slice of data, pagination is done here. + crdObjectCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toObjectCells(list.Items), dsQuery) + list.Items = fromObjectCells(crdObjectCells) + list.ListMeta = api.ListMeta{TotalItems: filteredTotal} + list.Errors = nonCriticalErrors + + return list, nil +} + +// GetCustomResourceObjectDetail returns details of a single object in a CR. +func GetCustomResourceObjectDetail(client apiextensionsclientset.Interface, namespace *common.NamespaceQuery, config *rest.Config, crdName string, name string) (CustomResourceObjectDetail, error) { + var detail CustomResourceObjectDetail + + customResourceDefinition, err := client.ApiextensionsV1beta1(). + CustomResourceDefinitions(). + Get(crdName, metav1.GetOptions{}) + if err != nil { + return detail, err + } + + restClient, err := newRESTClient(config, getCustomResourceDefinitionGroupVersion(customResourceDefinition)) + if err != nil { + return detail, err + } + + err = restClient.Get(). + Namespace(namespace.ToRequestParam()). + Resource(customResourceDefinition.Spec.Names.Plural). + Name(name).Do().Into(&detail) + if err != nil { + return detail, err + } + + return detail, nil +}