diff --git a/pkg/client/unversioned/helper.go b/pkg/client/unversioned/helper.go index d3ccd30d19b..ce046045ef3 100644 --- a/pkg/client/unversioned/helper.go +++ b/pkg/client/unversioned/helper.go @@ -17,6 +17,7 @@ limitations under the License. package unversioned import ( + "encoding/json" "fmt" "io/ioutil" "net" @@ -178,6 +179,65 @@ func MatchesServerVersion(client *Client, c *Config) error { return nil } +func extractGroupVersions(l *api.APIGroupList) []string { + var groupVersions []string + for _, g := range l.Groups { + for _, gv := range g.Versions { + groupVersions = append(groupVersions, gv.GroupVersion) + } + } + return groupVersions +} + +// ServerAPIVersions returns the GroupVersions supported by the API server. +// It creates a RESTClient based on the passed in config, but it doesn't rely +// on the Version, Codec, and Prefix of the config, because it uses AbsPath and +// takes the raw response. +func ServerAPIVersions(c *Config) (groupVersions []string, err error) { + transport, err := TransportFor(c) + if err != nil { + return nil, err + } + client := http.Client{Transport: transport} + + configCopy := *c + configCopy.Version = "" + configCopy.Prefix = "" + baseURL, err := defaultServerUrlFor(c) + if err != nil { + return nil, err + } + // Get the groupVersions exposed at /api + baseURL.Path = "/api" + resp, err := client.Get(baseURL.String()) + if err != nil { + return nil, err + } + var v api.APIVersions + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&v) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + + groupVersions = append(groupVersions, v.Versions...) + // Get the groupVersions exposed at /apis + baseURL.Path = "/apis" + resp2, err := client.Get(baseURL.String()) + if err != nil { + return nil, err + } + var apiGroupList api.APIGroupList + defer resp2.Body.Close() + err = json.NewDecoder(resp2.Body).Decode(&apiGroupList) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + groupVersions = append(groupVersions, extractGroupVersions(&apiGroupList)...) + + return groupVersions, nil +} + // NegotiateVersion queries the server's supported api versions to find // a version that both client and server support. // - If no version is provided, try registered client versions in order of @@ -471,9 +531,6 @@ func DefaultServerURL(host, prefix, version string, defaultTLS bool) (*url.URL, if host == "" { return nil, fmt.Errorf("host must be a URL or a host:port pair") } - if version == "" { - return nil, fmt.Errorf("version must be set") - } base := host hostURL, err := url.Parse(base) if err != nil { diff --git a/pkg/client/unversioned/helper_test.go b/pkg/client/unversioned/helper_test.go index 21c70a890cb..0b2642b91ef 100644 --- a/pkg/client/unversioned/helper_test.go +++ b/pkg/client/unversioned/helper_test.go @@ -17,11 +17,14 @@ limitations under the License. package unversioned import ( + "encoding/json" "net/http" + "net/http/httptest" "reflect" "strings" "testing" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" ) @@ -374,3 +377,59 @@ func TestSetKubernetesDefaultsUserAgent(t *testing.T) { t.Errorf("no user agent set: %#v", config) } } + +func TestHelperGetServerAPIVersions(t *testing.T) { + expect := []string{"v1", "v2", "v3"} + APIVersions := api.APIVersions{Versions: expect} + expect = append(expect, "group1/v1", "group1/v2", "group2/v1", "group2/v2") + APIGroupList := api.APIGroupList{ + Groups: []api.APIGroup{ + { + Versions: []api.GroupVersion{ + { + GroupVersion: "group1/v1", + }, + { + GroupVersion: "group1/v2", + }, + }, + }, + { + Versions: []api.GroupVersion{ + { + GroupVersion: "group2/v1", + }, + { + GroupVersion: "group2/v2", + }, + }, + }, + }, + } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + var output []byte + var err error + switch req.URL.Path { + case "/api": + output, err = json.Marshal(APIVersions) + + case "/apis": + output, err = json.Marshal(APIGroupList) + } + if err != nil { + t.Errorf("unexpected encoding error: %v", err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(output) + })) + got, err := ServerAPIVersions(&Config{Host: server.URL, Version: "invalid version", Codec: testapi.Default.Codec()}) + if err != nil { + t.Fatalf("unexpected encoding error: %v", err) + } + if e, a := expect, got; !reflect.DeepEqual(e, a) { + t.Errorf("expected %v, got %v", e, a) + } +}