之前在kubectl printer中已经分析过kubectl是如何以YAML格式打印内容的,本次分析将介绍kubectl是如何从YAML格式文件输入内容并做转换的。
FileVisitor
我们在使用kubectl create -f时,可以从文件创建资源,文件的格式可以为JSON格式或YAML格式。其中YAML格式支持”—“分割符,可以把多个YAML写在一个文件中。读取文件的操作通过FileVisitor进行,FileVisitor定义在/pkg/kubectl/resource/visitor.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| type FileVisitor struct { Path string *StreamVisitor } func (v *FileVisitor) Visit(fn VisitorFunc) error { var f *os.File if v.Path == constSTDINstr { f = os.Stdin } else { var err error if f, err = os.Open(v.Path); err != nil { return err } } defer f.Close() v.StreamVisitor.Reader = f return v.StreamVisitor.Visit(fn) }
|
可以看出,FileVisitor打开文件后,调用了StreamVisitor。StreamVisitor定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| type StreamVisitor struct { io.Reader *Mapper Source string Schema validation.Schema } func NewStreamVisitor(r io.Reader, mapper *Mapper, source string, schema validation.Schema) *StreamVisitor { return &StreamVisitor{ Reader: r, Mapper: mapper, Source: source, Schema: schema, } } func (v *StreamVisitor) Visit(fn VisitorFunc) error { d := yaml.NewYAMLOrJSONDecoder(v.Reader, 4096) for { ext := runtime.RawExtension{} if err := d.Decode(&ext); err != nil { if err == io.EOF { return nil } return err } ext.Raw = bytes.TrimSpace(ext.Raw) if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) { continue } if err := ValidateSchema(ext.Raw, v.Schema); err != nil { return fmt.Errorf("error validating %q: %v", v.Source, err) } info, err := v.InfoForData(ext.Raw, v.Source) if err != nil { if fnErr := fn(info, err); fnErr != nil { return fnErr } continue } if err := fn(info, nil); err != nil { return err } } }
|
可以看出,StreamVisitor的Visit()函数先通过调用NewYAMLOrJSONDecoder()生成decoder,然后使用Decode()对YAML文件中每一个YAML体进行解码。
YAMLOrJSONDecoder
所以,接下来来看YAMLOrJSONDecoder,定义在/pkg/util/yaml/decoder.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| type YAMLOrJSONDecoder struct { r io.Reader bufferSize int decoder decoder } func NewYAMLOrJSONDecoder(r io.Reader, bufferSize int) *YAMLOrJSONDecoder { return &YAMLOrJSONDecoder{ r: r, bufferSize: bufferSize, } } func (d *YAMLOrJSONDecoder) Decode(into interface{}) error { if d.decoder == nil { buffer, isJSON := GuessJSONStream(d.r, d.bufferSize) if isJSON { glog.V(4).Infof("decoding stream as JSON") d.decoder = json.NewDecoder(buffer) } else { glog.V(4).Infof("decoding stream as YAML") d.decoder = NewYAMLToJSONDecoder(buffer) } } err := d.decoder.Decode(into) if jsonDecoder, ok := d.decoder.(*json.Decoder); ok { if syntax, ok := err.(*json.SyntaxError); ok { data, readErr := ioutil.ReadAll(jsonDecoder.Buffered()) if readErr != nil { glog.V(4).Infof("reading stream failed: %v", readErr) } js := string(data) start := strings.LastIndex(js[:syntax.Offset], "\n") + 1 line := strings.Count(js[:start], "\n") return fmt.Errorf("json: line %d: %s", line, syntax.Error()) } } return err }
|
可以看出,YAMLOrJSONDecoder的Decode()方法会依据数据的格式来使用不同的解码器。其实判断是JSON格式还是YAML格式的方法很简单,以”{“开头的就认为是JSON格式,其他为YAML格式,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func GuessJSONStream(r io.Reader, size int) (io.Reader, bool) { buffer := bufio.NewReaderSize(r, size) b, _ := buffer.Peek(size) return buffer, hasJSONPrefix(b) } var jsonPrefix = []byte("{") func hasJSONPrefix(buf []byte) bool { return hasPrefix(buf, jsonPrefix) } func hasPrefix(buf []byte, prefix []byte) bool { trim := bytes.TrimLeftFunc(buf, unicode.IsSpace) return bytes.HasPrefix(trim, prefix) }
|
如果是YAML格式,YAMLOrJSONDecoder将通过NewYAMLToJSONDecoder()生成YAMLToJSONDecoder,然后调用YAMLToJSONDecoder的decode()方法。
YAMLToJSONDecoder
YAMLToJSONDecoder用来解码YAML文件。YAMLToJSONDecoder定义在/pkg/util/yaml/decoder.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| type YAMLToJSONDecoder struct { reader Reader } func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder { reader := bufio.NewReader(r) return &YAMLToJSONDecoder{ reader: NewYAMLReader(reader), } } func (d *YAMLToJSONDecoder) Decode(into interface{}) error { bytes, err := d.reader.Read() if err != nil && err != io.EOF { return err } if len(bytes) != 0 { data, err := yaml.YAMLToJSON(bytes) if err != nil { return err } return json.Unmarshal(data, into) } return err }
|
可以看出,YAMLToJSONDecoder的Decode()方法是通过reader.Read()来读取一个YAML体,然后使用yaml.YAMLToJSON()把YAML体转换成JSON格式,最后使用json.Unmarshal()来把JSON转换成结构体值。
所以现在的关键是reader.Read()是如何读取YAML文件的,又是如何处理分割符的。从NewYAMLToJSONDecoder()可以看出,YAMLToJSONDecoder的reader本质为YAMLReader。
YAMLReader
YAMLReader可以从数据流中读取一个YAML体,不同的YAML体是通过分割符”—“来区分的。YAMLReader定义在/pkg/util/yaml/decoder.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| type YAMLReader struct { reader Reader } func NewYAMLReader(r *bufio.Reader) *YAMLReader { return &YAMLReader{ reader: &LineReader{reader: r}, } } func (r *YAMLReader) Read() ([]byte, error) { var buffer bytes.Buffer for { line, err := r.reader.Read() if err != nil && err != io.EOF { return nil, err } sep := len([]byte(separator)) if i := bytes.Index(line, []byte(separator)); i == 0 { i += sep after := line[i:] if len(strings.TrimRightFunc(string(after), unicode.IsSpace)) == 0 { if buffer.Len() != 0 { return buffer.Bytes(), nil } if err == io.EOF { return nil, err } } } if err == io.EOF { if buffer.Len() != 0 { return buffer.Bytes(), nil } return nil, err } buffer.Write(line) } }
|
YAMLReader的Read()方法会对读取数据流中的数据,并写入到buffer中,如果遇到分割,则返回buffer中的内容;如果遇到结束符,也直接返回buffer内容。
由于数据流只有一个,所以YAMLReader的Reader()每次只读一个YAML体,第二次运行时将读第二个YAML体,依此类推。
YAMLReader是通过LineReader来读取行的。
LineReader
LineReader用于读取数据流中的一行,定义在/pkg/util/yaml/decoder.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| type LineReader struct { reader *bufio.Reader } func (r *LineReader) Read() ([]byte, error) { var ( isPrefix bool = true err error = nil line []byte buffer bytes.Buffer ) for isPrefix && err == nil { line, isPrefix, err = r.reader.ReadLine() buffer.Write(line) } buffer.WriteByte('\n') return buffer.Bytes(), err }
|
可以看出,LineReader的Read()通过调用bufio.Reader的ReadLine()方法读取一行,并在结尾加上’\n’。
分析结束。