istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/msg/generate.main.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  //go:build ignore
    16  // +build ignore
    17  
    18  package main
    19  
    20  import (
    21  	"bytes"
    22  	"fmt"
    23  	"os"
    24  	"regexp"
    25  	"text/template"
    26  
    27  	"sigs.k8s.io/yaml"
    28  )
    29  
    30  const (
    31  	codeRegex = `^IST\d\d\d\d$`
    32  	nameRegex = `^[[:upper:]]\w*$`
    33  )
    34  
    35  // Utility for generating messages.gen.go. Called from gen.go
    36  func main() {
    37  	if len(os.Args) != 3 {
    38  		fmt.Println("Invalid args:", os.Args)
    39  		os.Exit(-1)
    40  	}
    41  
    42  	input := os.Args[1]
    43  	output := os.Args[2]
    44  
    45  	m, err := read(input)
    46  	if err != nil {
    47  		fmt.Println("Error reading metadata:", err)
    48  		os.Exit(-2)
    49  	}
    50  
    51  	err = validate(m)
    52  	if err != nil {
    53  		fmt.Println("Error validating messages:", err)
    54  		os.Exit(-3)
    55  	}
    56  
    57  	code, err := generate(m)
    58  	if err != nil {
    59  		fmt.Println("Error generating code:", err)
    60  		os.Exit(-4)
    61  	}
    62  
    63  	if err = os.WriteFile(output, []byte(code), os.ModePerm); err != nil {
    64  		fmt.Println("Error writing output file:", err)
    65  		os.Exit(-5)
    66  	}
    67  }
    68  
    69  func read(path string) (*messages, error) {
    70  	b, err := os.ReadFile(path)
    71  	if err != nil {
    72  		return nil, fmt.Errorf("unable to read input file: %v", err)
    73  	}
    74  
    75  	m := &messages{}
    76  
    77  	if err := yaml.Unmarshal(b, m); err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	return m, nil
    82  }
    83  
    84  // Enforce that names and codes follow expected regex and are unique
    85  func validate(ms *messages) error {
    86  	codes := make(map[string]bool)
    87  	names := make(map[string]bool)
    88  
    89  	for _, m := range ms.Messages {
    90  		matched, err := regexp.MatchString(codeRegex, m.Code)
    91  		if err != nil {
    92  			return err
    93  		}
    94  		if !matched {
    95  			return fmt.Errorf("Error code for message %q must follow the regex %s", m.Name, codeRegex)
    96  		}
    97  
    98  		if codes[m.Code] {
    99  			return fmt.Errorf("Error codes must be unique, %q defined more than once", m.Code)
   100  		}
   101  		codes[m.Code] = true
   102  
   103  		matched, err = regexp.MatchString(nameRegex, m.Name)
   104  		if err != nil {
   105  			return err
   106  		}
   107  		if !matched {
   108  			return fmt.Errorf("Name for message %q must follow the regex %s", m.Name, nameRegex)
   109  		}
   110  
   111  		if names[m.Name] {
   112  			return fmt.Errorf("Message names must be unique, %q defined more than once", m.Name)
   113  		}
   114  		names[m.Name] = true
   115  	}
   116  	return nil
   117  }
   118  
   119  var tmpl = `
   120  // Code generated by generate.main.go. DO NOT EDIT.
   121  
   122  package msg
   123  
   124  import (
   125  	"istio.io/istio/pkg/config/analysis/diag"
   126  	"istio.io/istio/pkg/config/resource"
   127  )
   128  
   129  var (
   130  	{{- range .Messages}}
   131  	// {{.Name}} defines a diag.MessageType for message "{{.Name}}".
   132  	// Description: {{.Description}}
   133  	{{.Name}} = diag.NewMessageType(diag.{{.Level}}, "{{.Code}}", "{{.Template}}")
   134  	{{end}}
   135  )
   136  
   137  // All returns a list of all known message types.
   138  func All() []*diag.MessageType {
   139  	return []*diag.MessageType{
   140  		{{- range .Messages}}
   141  			{{.Name}},
   142  		{{- end}}
   143  	}
   144  }
   145  
   146  {{range .Messages}}
   147  // New{{.Name}} returns a new diag.Message based on {{.Name}}.
   148  func New{{.Name}}(r *resource.Instance{{range .Args}}, {{.Name}} {{.Type}}{{end}}) diag.Message {
   149  	return diag.NewMessage(
   150  		{{.Name}},
   151  		r,
   152  		{{- range .Args}}
   153  			{{.Name}},
   154  		{{- end}}
   155  	)
   156  }
   157  {{end}}
   158  `
   159  
   160  func generate(m *messages) (string, error) {
   161  	t := template.Must(template.New("code").Parse(tmpl))
   162  
   163  	var b bytes.Buffer
   164  	if err := t.Execute(&b, m); err != nil {
   165  		return "", err
   166  	}
   167  	return b.String(), nil
   168  }
   169  
   170  type messages struct {
   171  	Messages []message `json:"messages"`
   172  }
   173  
   174  type message struct {
   175  	Name        string `json:"name"`
   176  	Code        string `json:"code"`
   177  	Level       string `json:"level"`
   178  	Description string `json:"description"`
   179  	Template    string `json:"template"`
   180  	Url         string `json:"url"`
   181  	Args        []arg  `json:"args"`
   182  }
   183  
   184  type arg struct {
   185  	Name string `json:"name"`
   186  	Type string `json:"type"`
   187  }