k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/experiment/bumpmonitoring/main.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "os" 24 "path" 25 "path/filepath" 26 "regexp" 27 "strings" 28 29 flag "github.com/spf13/pflag" 30 31 "github.com/sirupsen/logrus" 32 "sigs.k8s.io/prow/cmd/generic-autobumper/bumper" 33 "sigs.k8s.io/yaml" 34 ) 35 36 const ( 37 defaultSrcPath = "../../config/prow/cluster/monitoring" 38 ) 39 40 var ( 41 configPathsToUpdate = map[string]*regexp.Regexp{ 42 "mixins/grafana_dashboards": regexp.MustCompile(`.*\.jsonnet$`), 43 "mixins/prometheus": regexp.MustCompile(`.*\.libsonnet$`), 44 } 45 configPathExcluded = []*regexp.Regexp{ 46 regexp.MustCompile(`mixins/prometheus/prometheus\.libsonnet`), 47 } 48 ) 49 50 type options struct { 51 SrcPath string `json:"srcPath"` 52 DstPath string `json:"dstPath"` 53 bumper.Options 54 } 55 56 func parseOptions() (*options, error) { 57 var config string 58 var labelsOverride []string 59 var skipPullRequest bool 60 var signoff bool 61 62 flag.StringVar(&config, "config", "", "The path to the config file for PR creation.") 63 flag.StringSliceVar(&labelsOverride, "labels-override", nil, "Override labels to be added to PR.") 64 flag.BoolVar(&skipPullRequest, "skip-pullrequest", false, "") 65 flag.BoolVar(&signoff, "signoff", false, "Signoff the commits.") 66 flag.Parse() 67 68 var o options 69 data, err := os.ReadFile(config) 70 if err != nil { 71 return nil, fmt.Errorf("read %q: %w", config, err) 72 } 73 if err = yaml.Unmarshal(data, &o); err != nil { 74 return nil, fmt.Errorf("unmarshal %q: %w", config, err) 75 } 76 77 if len(o.SrcPath) == 0 { 78 o.SrcPath = defaultSrcPath 79 } 80 if labelsOverride != nil { 81 o.Labels = labelsOverride 82 } 83 o.SkipPullRequest = skipPullRequest 84 o.Signoff = signoff 85 return &o, nil 86 } 87 88 func validateOptions(o *options) error { 89 if len(o.DstPath) == 0 { 90 return errors.New("dstPath is mandatory") 91 } 92 93 return nil 94 } 95 96 var _ bumper.PRHandler = (*client)(nil) 97 98 type client struct { 99 srcPath string 100 dstPath string 101 paths []string 102 } 103 104 // Changes returns a slice of functions, each one does some stuff, and 105 // returns commit message for the changes 106 func (c *client) Changes() []func(context.Context) (string, error) { 107 return []func(context.Context) (string, error){ 108 func(_ context.Context) (string, error) { 109 if err := c.findConfigToUpdate(); err != nil { 110 return "", err 111 } 112 113 if err := c.copyFiles(); err != nil { 114 return "", err 115 } 116 117 return strings.Join([]string{c.title(), c.body()}, "\n\n"), nil 118 }, 119 } 120 } 121 122 // PRTitleBody returns the body of the PR, this function runs after each commit 123 func (c *client) PRTitleBody() (string, string) { 124 return c.title(), c.body() 125 } 126 127 func (c *client) title() string { 128 return "Update monitoring stack" 129 } 130 131 func (c *client) body() string { 132 return fmt.Sprintf(`For code reviewers: 133 - breaking changes are only introduced in %s/mixins/lib/config.libsonnet 134 - presubmit test is expected to fail if there is any breaking change 135 - push to this change with fix if it's the case`, c.dstPath) 136 } 137 138 func (c *client) findConfigToUpdate() error { 139 for subPath, re := range configPathsToUpdate { 140 fullPath := path.Join(c.dstPath, subPath) 141 142 if _, err := os.Stat(fullPath); err != nil { 143 if !os.IsNotExist(err) { 144 return fmt.Errorf("failed to get the file info for %q: %w", fullPath, err) 145 } 146 logrus.Infof("Skipping %s as it doesn't exist from dst", fullPath) 147 } 148 149 // No error is expected 150 filepath.Walk(fullPath, func(leafPath string, info os.FileInfo, err error) error { 151 if !re.MatchString(leafPath) { 152 return nil 153 } 154 for _, reExcluded := range configPathExcluded { 155 if reExcluded.MatchString(leafPath) { 156 return nil 157 } 158 } 159 relPath, _ := filepath.Rel(c.dstPath, leafPath) 160 c.paths = append(c.paths, relPath) 161 return nil 162 }) 163 } 164 165 return nil 166 } 167 168 func (c *client) copyFiles() error { 169 for _, subPath := range c.paths { 170 SrcPath := path.Join(c.srcPath, subPath) 171 DstPath := path.Join(c.dstPath, subPath) 172 content, err := os.ReadFile(SrcPath) 173 if err != nil { 174 return fmt.Errorf("failed reading file %q: %w", SrcPath, err) 175 } 176 if err := os.WriteFile(DstPath, content, 0755); err != nil { 177 return fmt.Errorf("failed writing file %q: %w", DstPath, err) 178 } 179 } 180 return nil 181 } 182 183 func main() { 184 ctx := context.Background() 185 o, err := parseOptions() 186 if err != nil { 187 logrus.WithError(err).Fatalf("Failed to run the bumper tool") 188 } 189 if err := validateOptions(o); err != nil { 190 logrus.WithError(err).Fatalf("Failed validating flags") 191 } 192 193 c := client{ 194 srcPath: o.SrcPath, 195 dstPath: o.DstPath, 196 paths: make([]string, 0), 197 } 198 199 if err := bumper.Run(ctx, &o.Options, &c); err != nil { 200 logrus.Fatal(err) 201 } 202 }