k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/experiment/ci-janitor/main.go (about)

     1  /*
     2  Copyright 2018 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  // ci-janitor cleans up dedicated projects in k8s prowjob configs
    18  package main
    19  
    20  import (
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"os/exec"
    25  	"strings"
    26  
    27  	"github.com/sirupsen/logrus"
    28  	v1 "k8s.io/api/core/v1"
    29  	"sigs.k8s.io/prow/pkg/config"
    30  	configflagutil "sigs.k8s.io/prow/pkg/flagutil/config"
    31  )
    32  
    33  type options struct {
    34  	prowConfig  configflagutil.ConfigOptions
    35  	janitorPath string
    36  }
    37  
    38  var (
    39  	defaultTTL = 24
    40  	soakTTL    = 24 * 10
    41  	blocked    = []string{
    42  		"kubernetes-scale",  // Let it's up/down job handle the resources
    43  		"k8s-scale-testing", // As it can be running some manual experiments
    44  		// PR projects, migrate to boskos!
    45  		"k8s-jkns-pr-gci-gke",
    46  		"k8s-jkns-pr-gke",
    47  		"k8s-jkns-pr-kubemark",
    48  		"k8s-jkns-pr-node-e2e",
    49  		"k8s-jkns-pr-gce-gpus",
    50  		// k8s-infra projects, can't be cleaned by k8s-prow serviceaccounts
    51  		"k8s-infra-e2e-scale-5k-project",
    52  		"k8s-infra-e2e-gpu-project",
    53  	}
    54  )
    55  
    56  func (o *options) Validate() error {
    57  	if err := o.prowConfig.Validate(false); err != nil {
    58  		return err
    59  	}
    60  
    61  	if o.janitorPath == "" {
    62  		return errors.New("required flag --janitor-path was unset")
    63  	}
    64  
    65  	return nil
    66  }
    67  
    68  func gatherOptions() options {
    69  	o := options{}
    70  	o.prowConfig.AddFlags(flag.CommandLine)
    71  	flag.StringVar(&o.janitorPath, "janitor-path", "", "Path to gcp_janitor.py.")
    72  	flag.Parse()
    73  	return o
    74  }
    75  
    76  func containers(jb config.JobBase) []v1.Container {
    77  	var containers []v1.Container
    78  	if jb.Spec != nil {
    79  		containers = append(containers, jb.Spec.Containers...)
    80  		containers = append(containers, jb.Spec.InitContainers...)
    81  	}
    82  	return containers
    83  }
    84  
    85  func findProject(jb config.JobBase) (string, int) {
    86  	project := ""
    87  	ttl := defaultTTL
    88  	for _, container := range containers(jb) {
    89  		for _, arg := range container.Args {
    90  			if strings.HasPrefix(arg, "--gcp-project=") {
    91  				project = strings.TrimPrefix(arg, "--gcp-project=")
    92  			}
    93  
    94  			if arg == "--soak" {
    95  				ttl = soakTTL
    96  			}
    97  		}
    98  	}
    99  
   100  	return project, ttl
   101  }
   102  
   103  func clean(proj, janitorPath string, ttl int) error {
   104  	for _, bad := range blocked {
   105  		if bad == proj {
   106  			logrus.Infof("Will skip project %s", proj)
   107  			return nil
   108  		}
   109  	}
   110  
   111  	logrus.Infof("Will clean up %s with ttl %d h", proj, ttl)
   112  
   113  	cmd := exec.Command(janitorPath, fmt.Sprintf("--project=%s", proj), fmt.Sprintf("--hour=%d", ttl))
   114  	b, err := cmd.CombinedOutput()
   115  	if err != nil {
   116  		logrus.WithError(err).Errorf("failed to clean up project %s, error info: %s", proj, string(b))
   117  	} else {
   118  		logrus.Infof("successfully cleaned up project %s", proj)
   119  	}
   120  
   121  	return err
   122  }
   123  
   124  func main() {
   125  	o := gatherOptions()
   126  	if err := o.Validate(); err != nil {
   127  		logrus.Fatalf("Invalid options: %v", err)
   128  	}
   129  
   130  	agent, err := o.prowConfig.ConfigAgent()
   131  	if err != nil {
   132  		logrus.WithError(err).Fatal("Error loading config.")
   133  	}
   134  	conf := agent.Config()
   135  
   136  	failed := []string{}
   137  
   138  	var jobs []config.JobBase
   139  
   140  	for _, v := range conf.AllStaticPresubmits(nil) {
   141  		jobs = append(jobs, v.JobBase)
   142  	}
   143  	for _, v := range conf.AllStaticPostsubmits(nil) {
   144  		jobs = append(jobs, v.JobBase)
   145  	}
   146  	for _, v := range conf.AllPeriodics() {
   147  		jobs = append(jobs, v.JobBase)
   148  	}
   149  
   150  	for _, j := range jobs {
   151  		if project, ttl := findProject(j); project != "" {
   152  			if err := clean(project, o.janitorPath, ttl); err != nil {
   153  				logrus.WithError(err).Errorf("failed to clean %q", project)
   154  				failed = append(failed, project)
   155  			}
   156  		}
   157  	}
   158  
   159  	if len(failed) > 0 {
   160  		logrus.Fatalf("Failed clean %d projects: %v", len(failed), failed)
   161  	}
   162  
   163  	logrus.Info("Successfully cleaned up all projects!")
   164  }