// VersionedParams will take the provided object, serialize it to a map[string][]string using the // implicit RESTClient API version and the provided object convertor, and then add those as parameters // to the request. Use this to provide versioned query parameters from client libraries. func (r *Request) VersionedParams(obj runtime.Object, convertor runtime.ObjectConvertor) *Request { if r.err != nil { return r } versioned, err := convertor.ConvertToVersion(obj, r.groupVersion.String()) if err != nil { r.err = err return r } params, err := queryparams.Convert(versioned) if err != nil { r.err = err return r } for k, v := range params { for _, value := range v { // TODO: Move it to setParam method, once we get rid of // FieldSelectorParam & LabelSelectorParam methods. if k == unversioned.LabelSelectorQueryParam(r.groupVersion.String()) && value == "" { // Don't set an empty selector for backward compatibility. // Since there is no way to get the difference between empty // and unspecified string, we don't set it to avoid having // labelSelector= param in every request. continue } if k == unversioned.FieldSelectorQueryParam(r.groupVersion.String()) { if value == "" { // Don't set an empty selector for backward compatibility. // Since there is no way to get the difference between empty // and unspecified string, we don't set it to avoid having // fieldSelector= param in every request. continue } // TODO: Filtering should be handled somewhere else. selector, err := fields.ParseSelector(value) if err != nil { r.err = fmt.Errorf("unparsable field selector: %v", err) return r } filteredSelector, err := selector.Transform( func(field, value string) (newField, newValue string, err error) { return fieldMappings.filterField(r.groupVersion, r.resource, field, value) }) if err != nil { r.err = fmt.Errorf("untransformable field selector: %v", err) return r } value = filteredSelector.String() } r.setParam(k, value) } } return r }
// EncodeParameters converts the provided object into the to version, then converts that object to url.Values. // Returns an error if conversion is not possible. func (c *parameterCodec) EncodeParameters(obj Object, to unversioned.GroupVersion) (url.Values, error) { gvk, _, err := c.typer.ObjectKind(obj) if err != nil { return nil, err } if to != gvk.GroupVersion() { out, err := c.convertor.ConvertToVersion(obj, to) if err != nil { return nil, err } obj = out } return queryparams.Convert(obj) }
func (e *Streamer) setupRequestParameters(obj runtime.Object) error { versioned, err := api.Scheme.ConvertToVersion(obj, e.config.Version) if err != nil { return err } params, err := queryparams.Convert(versioned) if err != nil { return err } for k, v := range params { for _, vv := range v { e.req.Param(k, vv) } } return nil }
// VersionedParams will take the provided object, serialize it to a map[string][]string using the // implicit RESTClient API version and the provided object convertor, and then add those as parameters // to the request. Use this to provide versioned query parameters from client libraries. func (r *Request) VersionedParams(obj runtime.Object, convertor runtime.ObjectConvertor) *Request { if r.err != nil { return r } versioned, err := convertor.ConvertToVersion(obj, r.apiVersion) if err != nil { r.err = err return r } params, err := queryparams.Convert(versioned) if err != nil { r.err = err return r } for k, v := range params { for _, vv := range v { r.setParam(k, vv) } } return r }
func TestBinaryBuildRequestOptions(t *testing.T) { r := &newer.BinaryBuildRequestOptions{ AsFile: "Dockerfile", Commit: "abcdef", } versioned, err := knewer.Scheme.ConvertToVersion(r, "v1") if err != nil { t.Fatal(err) } params, err := queryparams.Convert(versioned) if err != nil { t.Fatal(err) } decoded := &older.BinaryBuildRequestOptions{} if err := knewer.Scheme.Convert(¶ms, decoded); err != nil { t.Fatal(err) } if decoded.Commit != "abcdef" || decoded.AsFile != "Dockerfile" { t.Errorf("unexpected decoded object: %#v", decoded) } }
func (parameterCodec) EncodeParameters(obj runtime.Object, to unversioned.GroupVersion) (url.Values, error) { return queryparams.Convert(obj) }
func TestConvert(t *testing.T) { sinceSeconds := int64(123) sinceTime := unversioned.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC) tests := []struct { input interface{} expected url.Values }{ { input: &foo{ Str: "hello", }, expected: url.Values{"str": {"hello"}}, }, { input: &foo{ Str: "test string", Slice: []string{"one", "two", "three"}, Integer: 234, Boolean: true, }, expected: url.Values{"str": {"test string"}, "slice": {"one", "two", "three"}, "integer": {"234"}, "boolean": {"true"}}, }, { input: &foo{ Str: "named types", NamedStr: "value1", NamedBool: true, }, expected: url.Values{"str": {"named types"}, "namedStr": {"value1"}, "namedBool": {"true"}}, }, { input: &foo{ Str: "don't ignore embedded struct", Foobar: bar{ Float1: 5.0, }, }, expected: url.Values{"str": {"don't ignore embedded struct"}, "float1": {"5"}, "float2": {"0"}}, }, { // Ignore untagged fields input: &bar{ Float1: 23.5, Float2: 100.7, Int1: 1, Int2: 2, Int3: 3, Ignored: 1, Ignored2: "ignored", }, expected: url.Values{"float1": {"23.5"}, "float2": {"100.7"}, "int1": {"1"}, "int2": {"2"}, "int3": {"3"}}, }, { // include fields that are not tagged omitempty input: &foo{ NamedStr: "named str", }, expected: url.Values{"str": {""}, "namedStr": {"named str"}}, }, { input: &baz{ Ptr: intp(5), Bptr: boolp(true), }, expected: url.Values{"ptr": {"5"}, "bptr": {"true"}}, }, { input: &baz{ Bptr: boolp(true), }, expected: url.Values{"ptr": {""}, "bptr": {"true"}}, }, { input: &baz{ Ptr: intp(5), }, expected: url.Values{"ptr": {"5"}}, }, { input: &childStructs{ Container: "mycontainer", Follow: true, Previous: true, SinceSeconds: &sinceSeconds, SinceTime: &sinceTime, // test a custom marshaller EmptyTime: nil, // test a nil custom marshaller without omitempty }, expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "sinceTime": {"2000-01-01T12:34:56Z"}, "emptyTime": {""}}, }, { input: &childStructs{ Container: "mycontainer", Follow: true, Previous: true, SinceSeconds: &sinceSeconds, SinceTime: nil, // test a nil custom marshaller with omitempty }, expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "emptyTime": {""}}, }, } for _, test := range tests { result, err := queryparams.Convert(test.input) if err != nil { t.Errorf("Unexpected error while converting %#v: %v", test.input, err) } validateResult(t, test.input, result, test.expected) } }
func TestConvert(t *testing.T) { tests := []struct { input interface{} expected url.Values }{ { input: &foo{ Str: "hello", }, expected: url.Values{"str": {"hello"}}, }, { input: &foo{ Str: "test string", Slice: []string{"one", "two", "three"}, Integer: 234, Boolean: true, }, expected: url.Values{"str": {"test string"}, "slice": {"one", "two", "three"}, "integer": {"234"}, "boolean": {"true"}}, }, { input: &foo{ Str: "named types", NamedStr: "value1", NamedBool: true, }, expected: url.Values{"str": {"named types"}, "namedStr": {"value1"}, "namedBool": {"true"}}, }, { input: &foo{ Str: "don't ignore embedded struct", Foobar: bar{ Float1: 5.0, }, }, expected: url.Values{"str": {"don't ignore embedded struct"}, "float1": {"5"}, "float2": {"0"}}, }, { // Ignore untagged fields input: &bar{ Float1: 23.5, Float2: 100.7, Int1: 1, Int2: 2, Int3: 3, Ignored: 1, Ignored2: "ignored", }, expected: url.Values{"float1": {"23.5"}, "float2": {"100.7"}, "int1": {"1"}, "int2": {"2"}, "int3": {"3"}}, }, { // include fields that are not tagged omitempty input: &foo{ NamedStr: "named str", }, expected: url.Values{"str": {""}, "namedStr": {"named str"}}, }, { input: &baz{ Ptr: intp(5), Bptr: boolp(true), }, expected: url.Values{"ptr": {"5"}, "bptr": {"true"}}, }, { input: &baz{ Bptr: boolp(true), }, expected: url.Values{"ptr": {""}, "bptr": {"true"}}, }, { input: &baz{ Ptr: intp(5), }, expected: url.Values{"ptr": {"5"}}, }, } for _, test := range tests { result, err := queryparams.Convert(test.input) if err != nil { t.Errorf("Unexpected error while converting %#v: %v", test.input, err) } validateResult(t, test.input, result, test.expected) } }
// Execute sends a remote command execution request, upgrading the // connection and creating streams to represent stdin/stdout/stderr. Data is // copied between these streams and the supplied stdin/stdout/stderr parameters. func (e *Executor) Execute() error { opts := api.PodExecOptions{ Stdin: (e.stdin != nil), Stdout: (e.stdout != nil), Stderr: (!e.tty && e.stderr != nil), TTY: e.tty, Command: e.command, } versioned, err := api.Scheme.ConvertToVersion(&opts, e.config.Version) if err != nil { return err } params, err := queryparams.Convert(versioned) if err != nil { return err } for k, v := range params { for _, vv := range v { e.req.Param(k, vv) } } if e.upgrader == nil { e.upgrader = &defaultUpgrader{} } conn, err := e.upgrader.upgrade(e.req, e.config) if err != nil { return err } defer conn.Close() doneChan := make(chan struct{}, 2) errorChan := make(chan error) cp := func(s string, dst io.Writer, src io.Reader) { glog.V(4).Infof("Copying %s", s) defer glog.V(4).Infof("Done copying %s", s) if _, err := io.Copy(dst, src); err != nil && err != io.EOF { glog.Errorf("Error copying %s: %v", s, err) } if s == api.StreamTypeStdout || s == api.StreamTypeStderr { doneChan <- struct{}{} } } headers := http.Header{} headers.Set(api.StreamType, api.StreamTypeError) errorStream, err := conn.CreateStream(headers) if err != nil { return err } go func() { message, err := ioutil.ReadAll(errorStream) if err != nil && err != io.EOF { errorChan <- fmt.Errorf("Error reading from error stream: %s", err) return } if len(message) > 0 { errorChan <- fmt.Errorf("Error executing remote command: %s", message) return } }() defer errorStream.Reset() if opts.Stdin { headers.Set(api.StreamType, api.StreamTypeStdin) remoteStdin, err := conn.CreateStream(headers) if err != nil { return err } defer remoteStdin.Reset() // TODO this goroutine will never exit cleanly (the io.Copy never unblocks) // because stdin is not closed until the process exits. If we try to call // stdin.Close(), it returns no error but doesn't unblock the copy. It will // exit when the process exits, instead. go cp(api.StreamTypeStdin, remoteStdin, e.stdin) } waitCount := 0 completedStreams := 0 if opts.Stdout { waitCount++ headers.Set(api.StreamType, api.StreamTypeStdout) remoteStdout, err := conn.CreateStream(headers) if err != nil { return err } defer remoteStdout.Reset() go cp(api.StreamTypeStdout, e.stdout, remoteStdout) } if opts.Stderr && !e.tty { waitCount++ headers.Set(api.StreamType, api.StreamTypeStderr) remoteStderr, err := conn.CreateStream(headers) if err != nil { return err } defer remoteStderr.Reset() go cp(api.StreamTypeStderr, e.stderr, remoteStderr) } Loop: for { select { case <-doneChan: completedStreams++ if completedStreams == waitCount { break Loop } case err := <-errorChan: return err } } return nil }