diff --git a/cluster/vagrant/util.sh b/cluster/vagrant/util.sh index c3c7774f0b9..c676782660d 100644 --- a/cluster/vagrant/util.sh +++ b/cluster/vagrant/util.sh @@ -92,7 +92,7 @@ function kube-up { local count="0" until [[ "$count" == "1" ]]; do local minions - minions=$("${KUBE_ROOT}/cluster/kubecfg.sh" -template '{{range.Items}}{{.Name}}:{{end}}' list minions) + minions=$("${KUBE_ROOT}/cluster/kubecfg.sh" -template '{{range.items}}{{.id}}:{{end}}' list minions) count=$(echo $minions | grep -c "${MINION_NAMES[i]}") || { printf "." sleep 2 diff --git a/cluster/validate-cluster.sh b/cluster/validate-cluster.sh index 82cca633d6d..fc5b151b128 100755 --- a/cluster/validate-cluster.sh +++ b/cluster/validate-cluster.sh @@ -33,7 +33,7 @@ detect-master > /dev/null detect-minions > /dev/null MINIONS_FILE=/tmp/minions -"${KUBE_ROOT}/cluster/kubecfg.sh" -template $'{{range.Items}}{{.Name}}\n{{end}}' list minions > ${MINIONS_FILE} +"${KUBE_ROOT}/cluster/kubecfg.sh" -template $'{{range.items}}{{.id}}\n{{end}}' list minions > ${MINIONS_FILE} # On vSphere, use minion IPs as their names if [[ "${KUBERNETES_PROVIDER}" == "vsphere" ]]; then diff --git a/cmd/kubecfg/kubecfg.go b/cmd/kubecfg/kubecfg.go index 936df408aa1..8f97cdf05ad 100644 --- a/cmd/kubecfg/kubecfg.go +++ b/cmd/kubecfg/kubecfg.go @@ -26,7 +26,6 @@ import ( "sort" "strconv" "strings" - "text/template" "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -324,14 +323,12 @@ func getPrinter() kubecfg.ResourcePrinter { } else { data = []byte(*templateStr) } - tmpl, err := template.New("output").Parse(string(data)) + var err error + printer, err = kubecfg.NewTemplatePrinter(data) if err != nil { - glog.Fatalf("Error parsing template %s, %v\n", string(data), err) + glog.Fatalf("Error '%v' parsing template:\n'%s'", err, string(data)) return nil } - printer = &kubecfg.TemplatePrinter{ - Template: tmpl, - } default: printer = humanReadablePrinter() } diff --git a/hack/e2e-suite/basic.sh b/hack/e2e-suite/basic.sh index 1f2d95507c7..3295967c000 100755 --- a/hack/e2e-suite/basic.sh +++ b/hack/e2e-suite/basic.sh @@ -37,7 +37,7 @@ function teardown() { trap "teardown" EXIT -pod_id_list=$($KUBECFG '-template={{range.Items}}{{.Name}} {{end}}' -l replicationController=my-hostname list pods) +pod_id_list=$($KUBECFG '-template={{range.items}}{{.id}} {{end}}' -l replicationController=my-hostname list pods) # Pod turn up on a clean cluster can take a while for the docker image pull. all_running=0 for i in $(seq 1 24); do @@ -45,7 +45,7 @@ for i in $(seq 1 24); do sleep 5 all_running=1 for id in $pod_id_list; do - current_status=$($KUBECFG -template '{{.CurrentState.Status}}' get pods/$id) || true + current_status=$($KUBECFG -template '{{.currentState.status}}' get pods/$id) || true if [[ "$current_status" != "Running" ]]; then all_running=0 break @@ -69,7 +69,7 @@ sleep 5 # Verify that something is listening. for id in ${pod_id_list}; do - ip=$($KUBECFG -template '{{.CurrentState.HostIP}}' get pods/$id) + ip=$($KUBECFG -template '{{.currentState.hostIP}}' get pods/$id) echo "Trying to reach server that should be running at ${ip}:8080..." ok=0 for i in $(seq 1 5); do diff --git a/hack/e2e-suite/guestbook.sh b/hack/e2e-suite/guestbook.sh index 1ffc256ca42..c70969d688b 100755 --- a/hack/e2e-suite/guestbook.sh +++ b/hack/e2e-suite/guestbook.sh @@ -35,7 +35,7 @@ $KUBECFG -c "${GUESTBOOK}/redis-slave-controller.json" create /replicationContro sleep 5 -POD_LIST_1=$($KUBECFG '-template={{range.Items}}{{.Name}} {{end}}' list pods) +POD_LIST_1=$($KUBECFG '-template={{range.items}}{{.id}} {{end}}' list pods) echo "Pods running: ${POD_LIST_1}" $KUBECFG stop redisSlaveController @@ -45,7 +45,7 @@ $KUBECFG rm redisSlaveController $KUBECFG delete services/redismaster $KUBECFG delete pods/redis-master-2 -POD_LIST_2=$($KUBECFG '-template={{range.Items}}{{.Name}} {{end}}' list pods) +POD_LIST_2=$($KUBECFG '-template={{range.items}}{{.id}} {{end}}' list pods) echo "Pods running after shutdown: ${POD_LIST_2}" exit 0 diff --git a/hack/e2e-suite/services.sh b/hack/e2e-suite/services.sh index 2750f1c6568..f4afc28656b 100755 --- a/hack/e2e-suite/services.sh +++ b/hack/e2e-suite/services.sh @@ -69,7 +69,7 @@ function query_pods() { local i for i in $(seq 1 10); do pods_unsorted=($(${KUBECFG} \ - '-template={{range.Items}}{{.Name}} {{end}}' \ + '-template={{range.items}}{{.id}} {{end}}' \ -l name="$1" list pods)) found="${#pods_unsorted[*]}" if [[ "${found}" == "$2" ]]; then @@ -103,7 +103,7 @@ function wait_for_pods() { echo "Waiting for ${pods_needed} pods to become 'running'" pods_needed="$2" for id in ${pods_sorted}; do - status=$(${KUBECFG} -template '{{.CurrentState.Status}}' get "pods/${id}") + status=$(${KUBECFG} -template '{{.currentState.status}}' get "pods/${id}") if [[ "${status}" == "Running" ]]; then pods_needed=$((pods_needed-1)) fi @@ -213,9 +213,9 @@ svc1_pods=$(query_pods "${svc1_name}" "${svc1_count}") svc2_pods=$(query_pods "${svc2_name}" "${svc2_count}") # Get the portal IPs. -svc1_ip=$(${KUBECFG} -template '{{.PortalIP}}' get "services/${svc1_name}") +svc1_ip=$(${KUBECFG} -template '{{.portalIP}}' get "services/${svc1_name}") test -n "${svc1_ip}" || error "Service1 IP is blank" -svc2_ip=$(${KUBECFG} -template '{{.PortalIP}}' get "services/${svc2_name}") +svc2_ip=$(${KUBECFG} -template '{{.portalIP}}' get "services/${svc2_name}") test -n "${svc2_ip}" || error "Service2 IP is blank" if [[ "${svc1_ip}" == "${svc2_ip}" ]]; then error "Portal IPs conflict: ${svc1_ip}" @@ -272,7 +272,7 @@ wait_for_pods "${svc3_name}" "${svc3_count}" svc3_pods=$(query_pods "${svc3_name}" "${svc3_count}") # Get the portal IP. -svc3_ip=$(${KUBECFG} -template '{{.PortalIP}}' get "services/${svc3_name}") +svc3_ip=$(${KUBECFG} -template '{{.portalIP}}' get "services/${svc3_name}") test -n "${svc3_ip}" || error "Service3 IP is blank" if [[ "${svc3_ip}" != "${svc1_ip}" ]]; then error "Portal IPs not resued: ${svc3_ip} != ${svc1_ip}" @@ -325,7 +325,7 @@ wait_for_pods "${svc4_name}" "${svc4_count}" svc4_pods=$(query_pods "${svc4_name}" "${svc4_count}") # Get the portal IP. -svc4_ip=$(${KUBECFG} -template '{{.PortalIP}}' get "services/${svc4_name}") +svc4_ip=$(${KUBECFG} -template '{{.portalIP}}' get "services/${svc4_name}") test -n "${svc4_ip}" || error "Service4 IP is blank" if [[ "${svc4_ip}" == "${svc2_ip}" || "${svc4_ip}" == "${svc3_ip}" ]]; then error "Portal IPs conflict: ${svc4_ip}" diff --git a/hack/e2e-suite/update.sh b/hack/e2e-suite/update.sh index d9263bb0596..9930dbc4e15 100755 --- a/hack/e2e-suite/update.sh +++ b/hack/e2e-suite/update.sh @@ -38,7 +38,7 @@ function validate() { sleep 2 local pod_id_list - pod_id_list=($($KUBECFG -template='{{range.Items}}{{.Name}} {{end}}' -l name="${CONTROLLER_NAME}" list pods)) + pod_id_list=($($KUBECFG -template='{{range.items}}{{.id}} {{end}}' -l name="${CONTROLLER_NAME}" list pods)) echo " ${#pod_id_list[@]} out of ${num_replicas} created" @@ -61,7 +61,7 @@ function validate() { # currently always set to a zero time. # # You can read about the syntax here: http://golang.org/pkg/text/template/ - template_string="{{and ((index .CurrentState.Info \"${CONTROLLER_NAME}\").State.Running) .CurrentState.Info.net.State.Running}}" + template_string="{{and ((index .currentState.info \"${CONTROLLER_NAME}\").state.running.startedAt) .currentState.info.net.state.running.startedAt}}" current_status=$($KUBECFG -template="${template_string}" get "pods/$id") || { if [[ $current_status =~ "pod \"${id}\" not found" ]]; then echo " $id no longer exists" @@ -73,12 +73,12 @@ function validate() { fi } - if [[ "$current_status" != "{0001-01-01 00:00:00 +0000 UTC}" ]]; then + if [[ "$current_status" != "0001-01-01T00:00:00Z" ]]; then echo " $id is created but not running" continue fi - template_string="{{(index .CurrentState.Info \"${CONTROLLER_NAME}\").Image}}" + template_string="{{(index .currentState.info \"${CONTROLLER_NAME}\").image}}" current_image=$($KUBECFG -template="${template_string}" get "pods/$id") if [[ "$current_image" != "${DOCKER_HUB_USER}/update-demo:${container_image_version}" ]]; then echo " ${id} is created but running wrong image" @@ -86,7 +86,7 @@ function validate() { fi - host_ip=$($KUBECFG -template='{{.CurrentState.HostIP}}' get pods/$id) + host_ip=$($KUBECFG -template='{{.currentState.hostIP}}' get pods/$id) curl -s --max-time 5 --fail http://${host_ip}:8080/data.json \ | grep -q ${container_image_version} || { echo " ${id} is running the right image but curl to contents failed or returned wrong info" diff --git a/hack/e2e.go b/hack/e2e.go index a6d708ac031..71fdce49c3d 100644 --- a/hack/e2e.go +++ b/hack/e2e.go @@ -40,6 +40,9 @@ var ( tests = flag.String("tests", "", "Run only tests in hack/e2e-suite matching this glob. Ignored if -test is set.") root = flag.String("root", absOrDie(filepath.Clean(filepath.Join(path.Base(os.Args[0]), ".."))), "Root directory of kubernetes repository.") verbose = flag.Bool("v", false, "If true, print all command output.") + + cfgCmd = flag.String("cfg", "", "If nonempty, pass this as an argument, and call kubecfg. Implies -v.") + ctlCmd = flag.String("ctl", "", "If nonempty, pass this as an argument, and call kubectl. Implies -v. (-test, -cfg, -ctl are mutually exclusive)") ) var signals = make(chan os.Signal, 100) @@ -87,18 +90,24 @@ func main() { } } - failed, passed := []string{}, []string{} - if *tests != "" { - failed, passed = Test() + failure := false + switch { + case *cfgCmd != "": + failure = !runBash("'kubecfg "+*cfgCmd+"'", "$KUBECFG "+*cfgCmd) + case *ctlCmd != "": + failure = !runBash("'kubectl "+*ctlCmd+"'", "$KUBECFG "+*ctlCmd) + case *tests != "": + failed, passed := Test() + log.Printf("Passed tests: %v", passed) + log.Printf("Failed tests: %v", failed) + failure = len(failed) > 0 } if *down { TearDown() } - log.Printf("Passed tests: %v", passed) - log.Printf("Failed tests: %v", failed) - if len(failed) > 0 { + if failure { os.Exit(1) } } diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 5c1d9db3b8c..e2cb665f6ac 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -100,12 +100,12 @@ kube::log::status "Testing kubectl(pods)" "${kube_cmd[@]}" create -f examples/guestbook/redis-master.json "${kube_flags[@]}" "${kube_cmd[@]}" get pods "${kube_flags[@]}" "${kube_cmd[@]}" get pod redis-master-2 "${kube_flags[@]}" -[[ "$("${kube_cmd[@]}" get pod redis-master-2 -o template --output-version=v1beta1 -t '{{ .ID }}' "${kube_flags[@]}")" == "redis-master-2" ]] +[[ "$("${kube_cmd[@]}" get pod redis-master-2 -o template --output-version=v1beta1 -t '{{ .id }}' "${kube_flags[@]}")" == "redis-master-2" ]] output_pod=$("${kube_cmd[@]}" get pod redis-master-2 -o json --output-version=v1beta1 "${kube_flags[@]}") "${kube_cmd[@]}" delete pod redis-master-2 "${kube_flags[@]}" -[[ $("${kube_cmd[@]}" get pods -o template -t '{{ len .Items }}' "${kube_flags[@]}") -eq 0 ]] +[[ $("${kube_cmd[@]}" get pods -o template -t '{{ len .items }}' "${kube_flags[@]}") -eq 0 ]] echo $output_pod | "${kube_cmd[@]}" create -f - "${kube_flags[@]}" -[[ $("${kube_cmd[@]}" get pods -o template -t '{{ len .Items }}' "${kube_flags[@]}") -eq 1 ]] +[[ $("${kube_cmd[@]}" get pods -o template -t '{{ len .items }}' "${kube_flags[@]}") -eq 1 ]] "${kube_cmd[@]}" get pods -o yaml "${kube_flags[@]}" | grep -q "id: redis-master-2" "${kube_cmd[@]}" describe pod redis-master-2 "${kube_flags[@]}" | grep -q 'Name:.*redis-master-2' "${kube_cmd[@]}" delete -f examples/guestbook/redis-master.json "${kube_flags[@]}" diff --git a/pkg/kubecfg/resource_printer.go b/pkg/kubecfg/resource_printer.go index 048e0327a9d..64ce0915d72 100644 --- a/pkg/kubecfg/resource_printer.go +++ b/pkg/kubecfg/resource_printer.go @@ -319,19 +319,32 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er // TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template. type TemplatePrinter struct { - Template *template.Template + template *template.Template +} + +func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) { + t, err := template.New("output").Parse(string(tmpl)) + if err != nil { + return nil, err + } + return &TemplatePrinter{t}, nil } // Print parses the data as JSON, and re-formats it with the Go Template. func (t *TemplatePrinter) Print(data []byte, w io.Writer) error { - obj, err := latest.Codec.Decode(data) + obj := map[string]interface{}{} + err := json.Unmarshal(data, &obj) if err != nil { return err } - return t.PrintObj(obj, w) + return t.template.Execute(w, obj) } // PrintObj formats the obj with the Go Template. func (t *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error { - return t.Template.Execute(w, obj) + data, err := latest.Codec.Encode(obj) + if err != nil { + return err + } + return t.Print(data, w) } diff --git a/pkg/kubecfg/resource_printer_test.go b/pkg/kubecfg/resource_printer_test.go index 2e05228af89..ef16bdc0884 100644 --- a/pkg/kubecfg/resource_printer_test.go +++ b/pkg/kubecfg/resource_printer_test.go @@ -161,3 +161,19 @@ func TestUnknownTypePrinting(t *testing.T) { t.Errorf("An error was expected from printing unknown type") } } + +func TestTemplateEmitsVersionedObjects(t *testing.T) { + // kind is always blank in memory and set on the wire + printer, err := NewTemplatePrinter([]byte(`{{.kind}}`)) + if err != nil { + t.Fatalf("tmpl fail: %v", err) + } + buffer := &bytes.Buffer{} + err = printer.PrintObj(&api.Pod{}, buffer) + if err != nil { + t.Fatalf("print fail: %v", err) + } + if e, a := "Pod", string(buffer.Bytes()); e != a { + t.Errorf("Expected %v, got %v", e, a) + } +} diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index ab95e5adb72..1fa60a51dd2 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -28,6 +28,7 @@ import ( "text/template" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/golang/glog" @@ -45,35 +46,26 @@ func GetPrinter(format, templateFile string, defaultPrinter ResourcePrinter) (Re case "yaml": printer = &YAMLPrinter{} case "template": - var data []byte if len(templateFile) == 0 { return nil, false, fmt.Errorf("template format specified but no template given") } - tmpl, err := template.New("output").Parse(templateFile) + var err error + printer, err = NewTemplatePrinter([]byte(templateFile)) if err != nil { - return nil, false, fmt.Errorf("error parsing template %s, %v\n", string(data), err) - } - printer = &TemplatePrinter{ - Template: tmpl, + return nil, false, fmt.Errorf("error parsing template %s, %v\n", templateFile, err) } case "templatefile": - var data []byte - if len(templateFile) > 0 { - var err error - data, err = ioutil.ReadFile(templateFile) - if err != nil { - return nil, false, fmt.Errorf("error reading template %s, %v\n", templateFile, err) - } - } else { + if len(templateFile) == 0 { return nil, false, fmt.Errorf("templatefile format specified but no template file given") } - tmpl, err := template.New("output").Parse(string(data)) + data, err := ioutil.ReadFile(templateFile) + if err != nil { + return nil, false, fmt.Errorf("error reading template %s, %v\n", templateFile, err) + } + printer, err = NewTemplatePrinter(data) if err != nil { return nil, false, fmt.Errorf("error parsing template %s, %v\n", string(data), err) } - printer = &TemplatePrinter{ - Template: tmpl, - } case "": printer = defaultPrinter versioned = false @@ -323,12 +315,29 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er // TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template. type TemplatePrinter struct { - Template *template.Template + template *template.Template +} + +func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) { + t, err := template.New("output").Parse(string(tmpl)) + if err != nil { + return nil, err + } + return &TemplatePrinter{t}, nil } // PrintObj formats the obj with the Go Template. func (t *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error { - return t.Template.Execute(w, obj) + data, err := latest.Codec.Encode(obj) + if err != nil { + return err + } + outObj := map[string]interface{}{} + err = json.Unmarshal(data, &outObj) + if err != nil { + return err + } + return t.template.Execute(w, outObj) } func tabbedString(f func(*tabwriter.Writer) error) (string, error) { diff --git a/pkg/kubectl/resource_printer_test.go b/pkg/kubectl/resource_printer_test.go index 5cf4aacee00..fc7c6b6bd8d 100644 --- a/pkg/kubectl/resource_printer_test.go +++ b/pkg/kubectl/resource_printer_test.go @@ -86,14 +86,17 @@ func TestPrintYAML(t *testing.T) { func TestPrintTemplate(t *testing.T) { buf := bytes.NewBuffer([]byte{}) - printer, versioned, err := GetPrinter("template", "{{ .Name }}", nil) + printer, versioned, err := GetPrinter("template", "{{.id}}", nil) if err != nil { - t.Errorf("unexpected error: %#v", err) + t.Fatalf("unexpected error: %#v", err) } if !versioned { t.Errorf("printer should be versioned") } - printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf) + err = printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf) + if err != nil { + t.Fatalf("unexpected error: %#v", err) + } if buf.String() != "foo" { t.Errorf("unexpected output: %s", buf.String()) } @@ -204,3 +207,19 @@ func TestUnknownTypePrinting(t *testing.T) { t.Errorf("An error was expected from printing unknown type") } } + +func TestTemplateEmitsVersionedObjects(t *testing.T) { + // kind is always blank in memory and set on the wire + printer, err := NewTemplatePrinter([]byte(`{{.kind}}`)) + if err != nil { + t.Fatalf("tmpl fail: %v", err) + } + buffer := &bytes.Buffer{} + err = printer.PrintObj(&api.Pod{}, buffer) + if err != nil { + t.Fatalf("print fail: %v", err) + } + if e, a := "Pod", string(buffer.Bytes()); e != a { + t.Errorf("Expected %v, got %v", e, a) + } +}