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  }