Skip to content

Commit

Permalink
Merge pull request kubernetes#627 from lavalamp/fix
Browse files Browse the repository at this point in the history
Add /version to server and check it in client.
  • Loading branch information
smarterclayton committed Jul 30, 2014
2 parents b0689b0 + 3b84880 commit dc6fdc4
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 30 deletions.
65 changes: 44 additions & 21 deletions cmd/kubecfg/kubecfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"io/ioutil"
"net/url"
"os"
"reflect"
"sort"
"strconv"
"strings"
Expand All @@ -31,28 +32,28 @@ import (
kube_client "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/golang/glog"
)

// AppVersion is the current version of kubecfg.
const AppVersion = "0.1"

var (
versionFlag = flag.Bool("V", false, "Print the version number.")
httpServer = flag.String("h", "", "The host to connect to.")
config = flag.String("c", "", "Path to the config file.")
selector = flag.String("l", "", "Selector (label query) to use for listing")
updatePeriod = flag.Duration("u", 60*time.Second, "Update interval period")
portSpec = flag.String("p", "", "The port spec, comma-separated list of <external>:<internal>,...")
servicePort = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
authConfig = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user. Only used if doing https.")
json = flag.Bool("json", false, "If true, print raw JSON for responses")
yaml = flag.Bool("yaml", false, "If true, print raw YAML for responses")
verbose = flag.Bool("verbose", false, "If true, print extra information")
proxy = flag.Bool("proxy", false, "If true, run a proxy to the api server")
www = flag.String("www", "", "If -proxy is true, use this directory to serve static files")
templateFile = flag.String("template_file", "", "If present, load this file as a golang template and use it for output printing")
templateStr = flag.String("template", "", "If present, parse this string as a golang template and use it for output printing")
versionFlag = flag.Bool("V", false, "Print the version number.")
serverVersion = flag.Bool("server_version", false, "Print the server's version number.")
preventSkew = flag.Bool("expect_version_match", false, "Fail if server's version doesn't match own version.")
httpServer = flag.String("h", "", "The host to connect to.")
config = flag.String("c", "", "Path to the config file.")
selector = flag.String("l", "", "Selector (label query) to use for listing")
updatePeriod = flag.Duration("u", 60*time.Second, "Update interval period")
portSpec = flag.String("p", "", "The port spec, comma-separated list of <external>:<internal>,...")
servicePort = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
authConfig = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user. Only used if doing https.")
json = flag.Bool("json", false, "If true, print raw JSON for responses")
yaml = flag.Bool("yaml", false, "If true, print raw YAML for responses")
verbose = flag.Bool("verbose", false, "If true, print extra information")
proxy = flag.Bool("proxy", false, "If true, run a proxy to the api server")
www = flag.String("www", "", "If -proxy is true, use this directory to serve static files")
templateFile = flag.String("template_file", "", "If present, load this file as a golang template and use it for output printing")
templateStr = flag.String("template", "", "If present, parse this string as a golang template and use it for output printing")
)

func usage() {
Expand Down Expand Up @@ -107,7 +108,7 @@ func main() {
defer util.FlushLogs()

if *versionFlag {
fmt.Println("Version:", AppVersion)
fmt.Printf("Version: %#v\n", version.Get())
os.Exit(0)
}

Expand Down Expand Up @@ -136,6 +137,30 @@ func main() {
}
}

client := kube_client.New(masterServer, auth)

if *serverVersion {
got, err := client.ServerVersion()
if err != nil {
fmt.Printf("Couldn't read version from server: %v\n", err)
os.Exit(1)
}
fmt.Printf("Server Version: %#v\n", got)
os.Exit(0)
}

if *preventSkew {
got, err := client.ServerVersion()
if err != nil {
fmt.Printf("Couldn't read version from server: %v\n", err)
os.Exit(1)
}
if c, s := version.Get(), *got; !reflect.DeepEqual(c, s) {
fmt.Printf("Server version (%#v) differs from client version (%#v)!\n", s, c)
os.Exit(1)
}
}

if *proxy {
glog.Info("Starting to serve on localhost:8001")
server := kubecfg.NewProxyServer(*www, masterServer, auth)
Expand All @@ -148,8 +173,6 @@ func main() {
}
method := flag.Arg(0)

client := kube_client.New(masterServer, auth)

matchFound := executeAPIRequest(method, client) || executeControllerRequest(method, client)
if matchFound == false {
glog.Fatalf("Unknown command %s", method)
Expand Down
2 changes: 1 addition & 1 deletion hack/e2e-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ set -e
# Use testing config
export KUBE_CONFIG_FILE="config-test.sh"
export KUBE_REPO_ROOT="$(dirname $0)/.."
export CLOUDCFG="${KUBE_REPO_ROOT}/cluster/kubecfg.sh"
export CLOUDCFG="${KUBE_REPO_ROOT}/cluster/kubecfg.sh -expect_version_match"

# Build a release required by the test provider [if any]
test-build-release
Expand Down
4 changes: 2 additions & 2 deletions hack/test-cmd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ APISERVER_PID=$!

wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver: "

KUBE_CMD="${GO_OUT}/kubecfg -h http://127.0.0.1:${API_PORT}"
KUBE_CMD="${GO_OUT}/kubecfg -h http://127.0.0.1:${API_PORT} -expect_version_match"

${KUBE_CMD} list pods
echo "kubecfg(pods): ok"
Expand All @@ -130,4 +130,4 @@ echo "kubecfg(minions): ok"
#PROXY_LOG=/tmp/kube-proxy.log
#${GO_OUT}/proxy \
# --etcd_servers="http://127.0.0.1:${ETCD_PORT}" 1>&2 &
#PROXY_PID=$!
#PROXY_PID=$!
22 changes: 21 additions & 1 deletion pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/golang/glog"
)
Expand Down Expand Up @@ -151,6 +152,7 @@ func New(storage map[string]RESTStorage, prefix string) *APIServer {
healthz.InstallHandler(s.mux)

s.mux.HandleFunc("/", s.handleIndex)
s.mux.HandleFunc("/version", s.handleVersionReq)

// Handle both operations and operations/* with the same handler
s.mux.HandleFunc(s.operationPrefix(), s.handleOperationRequest)
Expand Down Expand Up @@ -182,6 +184,11 @@ func (server *APIServer) handleIndex(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, data)
}

// handleVersionReq writes the server's version information.
func (server *APIServer) handleVersionReq(w http.ResponseWriter, req *http.Request) {
server.writeRawJSON(http.StatusOK, version.Get(), w)
}

func (server *APIServer) handleMinionReq(w http.ResponseWriter, req *http.Request) {
minionPrefix := "/proxy/minion/"
if !strings.HasPrefix(req.URL.Path, minionPrefix) {
Expand Down Expand Up @@ -353,14 +360,27 @@ func (server *APIServer) notFound(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Not Found: %#v", req)
}

// write writes an API object in wire format.
func (server *APIServer) write(statusCode int, object interface{}, w http.ResponseWriter) {
output, err := api.Encode(object)
if err != nil {
server.error(err, w)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
output, err := api.Encode(object)
w.Write(output)
}

// writeRawJSON writes a non-API object in JSON.
func (server *APIServer) writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
output, err := json.Marshal(object)
if err != nil {
server.error(err, w)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
w.Write(output)
}

Expand Down
16 changes: 16 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package client

import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
Expand All @@ -26,6 +27,7 @@ import (

"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/golang/glog"
)

Expand Down Expand Up @@ -238,3 +240,17 @@ func (c *Client) UpdateService(svc api.Service) (result api.Service, err error)
func (c *Client) DeleteService(name string) error {
return c.Delete().Path("services").Path(name).Do().Error()
}

// ServerVersion retrieves and parses the server's version.
func (c *Client) ServerVersion() (*version.Info, error) {
body, err := c.Get().AbsPath("/version").Do().Raw()
if err != nil {
return nil, err
}
var info version.Info
err = json.Unmarshal(body, &info)
if err != nil {
return nil, fmt.Errorf("Got '%s': %v", string(body), err)
}
return &info, nil
}
29 changes: 29 additions & 0 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package client

import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
Expand All @@ -26,6 +27,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
)

// TODO: Move this to a common place, it's needed in multiple tests.
Expand Down Expand Up @@ -456,3 +458,30 @@ func TestDoRequestAcceptedSuccess(t *testing.T) {
}
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
}

func TestGetServerVersion(t *testing.T) {
expect := version.Info{
Major: "foo",
Minor: "bar",
GitCommit: "baz",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
output, err := json.Marshal(expect)
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)
}))
client := New(server.URL, nil)

got, err := client.ServerVersion()
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)
}
}
14 changes: 14 additions & 0 deletions pkg/kubecfg/kubecfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,24 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/golang/glog"
"gopkg.in/v1/yaml"
)

func GetServerVersion(client *client.Client) (*version.Info, error) {
body, err := client.Get().AbsPath("/version").Do().Raw()
if err != nil {
return nil, err
}
var info version.Info
err = json.Unmarshal(body, &info)
if err != nil {
return nil, fmt.Errorf("Got '%s': %v", string(body), err)
}
return &info, nil
}

func promptForString(field string, r io.Reader) string {
fmt.Printf("Please enter %s: ", field)
var result string
Expand Down
6 changes: 3 additions & 3 deletions pkg/kubecfg/kubecfg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,9 @@ func TestMakePorts(t *testing.T) {
{
"8080:80,8081:8081,443:444",
[]api.Port{
api.Port{HostPort: 8080, ContainerPort: 80},
api.Port{HostPort: 8081, ContainerPort: 8081},
api.Port{HostPort: 443, ContainerPort: 444},
{HostPort: 8080, ContainerPort: 80},
{HostPort: 8081, ContainerPort: 8081},
{HostPort: 443, ContainerPort: 444},
},
},
}
Expand Down
19 changes: 17 additions & 2 deletions pkg/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ limitations under the License.

package version

func Get() (major, minor, gitCommit string) {
return "v1beta", "1", commitFromGit
// Info contains versioning information.
// TODO: Add []string of api versions supported? It's still unclear
// how we'll want to distribute that information.
type Info struct {
Major string `json:"major" yaml:"major"`
Minor string `json:"minor" yaml:"minor"`
GitCommit string `json:"gitCommit" yaml:"gitCommit"`
}

// Get returns the overall codebase version. It's for detecting
// what code a binary was built from.
func Get() Info {
return Info{
Major: "0",
Minor: "1",
GitCommit: commitFromGit,
}
}

0 comments on commit dc6fdc4

Please sign in to comment.