istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/util/yml/file.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package yml
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"strings"
    21  
    22  	"istio.io/istio/pkg/test"
    23  )
    24  
    25  type docType string
    26  
    27  const (
    28  	namespacesAndCRDs docType = "namespaces_and_crds"
    29  	misc              docType = "misc"
    30  )
    31  
    32  // FileWriter write YAML content to files.
    33  type FileWriter interface {
    34  	// WriteYAML writes the given YAML content to one or more YAML files.
    35  	WriteYAML(filenamePrefix string, contents ...string) ([]string, error)
    36  
    37  	// WriteYAMLOrFail calls WriteYAML and fails the test if an error occurs.
    38  	WriteYAMLOrFail(t test.Failer, filenamePrefix string, contents ...string) []string
    39  }
    40  
    41  type writerImpl struct {
    42  	workDir string
    43  }
    44  
    45  // NewFileWriter creates a new FileWriter that stores files under workDir.
    46  func NewFileWriter(workDir string) FileWriter {
    47  	return &writerImpl{
    48  		workDir: workDir,
    49  	}
    50  }
    51  
    52  // WriteYAML writes the given YAML content to one or more YAML files.
    53  func (w *writerImpl) WriteYAML(filenamePrefix string, contents ...string) ([]string, error) {
    54  	out := make([]string, 0, len(contents))
    55  	content := JoinString(contents...)
    56  	files, err := splitContentsToFiles(w.workDir, content, filenamePrefix)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	if len(files) == 0 {
    62  		f, err := writeContentsToTempFile(w.workDir, content)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  		files = append(files, f)
    67  	}
    68  	out = append(out, files...)
    69  	return out, nil
    70  }
    71  
    72  // WriteYAMLOrFial calls WriteYAML and fails the test if an error occurs.
    73  func (w *writerImpl) WriteYAMLOrFail(t test.Failer, filenamePrefix string, contents ...string) []string {
    74  	t.Helper()
    75  	out, err := w.WriteYAML(filenamePrefix, contents...)
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  	return out
    80  }
    81  
    82  func writeContentsToTempFile(workDir, contents string) (filename string, err error) {
    83  	defer func() {
    84  		if err != nil && filename != "" {
    85  			_ = os.Remove(filename)
    86  			filename = ""
    87  		}
    88  	}()
    89  
    90  	var f *os.File
    91  	f, err = os.CreateTemp(workDir, yamlToFilename(contents)+".*.yaml")
    92  	if err != nil {
    93  		return
    94  	}
    95  	defer f.Close()
    96  	filename = f.Name()
    97  
    98  	_, err = f.WriteString(contents)
    99  	return
   100  }
   101  
   102  func yamlToFilename(contents string) string {
   103  	spl := SplitYamlByKind(contents)
   104  	delete(spl, "")
   105  	types := []string{}
   106  	for k := range spl {
   107  		types = append(types, k)
   108  	}
   109  	switch len(types) {
   110  	case 0:
   111  		return "empty"
   112  	case 1:
   113  		m := GetMetadata(contents)
   114  		if len(m) == 0 {
   115  			return fmt.Sprintf("%s.%s", types[0], m[0].Name)
   116  		}
   117  		return types[0]
   118  	case 2, 3, 4:
   119  		return strings.Join(types, "-")
   120  	default:
   121  		return strings.Join(types[:4], "-") + "-more"
   122  	}
   123  }
   124  
   125  func splitContentsToFiles(workDir, content, filenamePrefix string) ([]string, error) {
   126  	split := SplitYamlByKind(content)
   127  	namespacesAndCrds := &yamlDoc{
   128  		docType: namespacesAndCRDs,
   129  		content: split["Namespace"],
   130  	}
   131  	misc := &yamlDoc{
   132  		docType: misc,
   133  		content: split["CustomResourceDefinition"],
   134  	}
   135  
   136  	// If all elements were put into a single doc just return an empty list, indicating that the original
   137  	// content should be used.
   138  	docs := []*yamlDoc{namespacesAndCrds, misc}
   139  	for _, doc := range docs {
   140  		if len(doc.content) == 0 {
   141  			return make([]string, 0), nil
   142  		}
   143  	}
   144  
   145  	filesToApply := make([]string, 0, len(docs))
   146  	for _, doc := range docs {
   147  		tfile, err := doc.toTempFile(workDir, filenamePrefix)
   148  		if err != nil {
   149  			return nil, err
   150  		}
   151  		filesToApply = append(filesToApply, tfile)
   152  	}
   153  	return filesToApply, nil
   154  }
   155  
   156  type yamlDoc struct {
   157  	content string
   158  	docType docType
   159  }
   160  
   161  func (d *yamlDoc) toTempFile(workDir, fileNamePrefix string) (string, error) {
   162  	f, err := os.CreateTemp(workDir, fmt.Sprintf("%s_%s.yaml", fileNamePrefix, d.docType))
   163  	if err != nil {
   164  		return "", err
   165  	}
   166  	defer func() { _ = f.Close() }()
   167  
   168  	name := f.Name()
   169  
   170  	_, err = f.WriteString(d.content)
   171  	if err != nil {
   172  		return "", err
   173  	}
   174  	return name, nil
   175  }