github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/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  	"k8s.io/api/core/v1"
    29  	"k8s.io/test-infra/prow/config"
    30  )
    31  
    32  type options struct {
    33  	configPath    string
    34  	jobConfigPath string
    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-gce",
    46  		"k8s-jkns-pr-gce-bazel",
    47  		"k8s-jkns-pr-gce-etcd3",
    48  		"k8s-jkns-pr-gci-gce",
    49  		"k8s-jkns-pr-gci-gke",
    50  		"k8s-jkns-pr-gci-kubemark",
    51  		"k8s-jkns-pr-gke",
    52  		"k8s-jkns-pr-kubeadm",
    53  		"k8s-jkns-pr-kubemark",
    54  		"k8s-jkns-pr-node-e2e",
    55  		"k8s-jkns-pr-gce-gpus",
    56  		"k8s-gke-gpu-pr",
    57  		"k8s-presubmit-scale",
    58  	}
    59  )
    60  
    61  func (o *options) Validate() error {
    62  	if o.configPath == "" {
    63  		return errors.New("required flag --config-path was unset")
    64  	}
    65  
    66  	if o.jobConfigPath == "" {
    67  		return errors.New("required flag --job-config-path was unset")
    68  	}
    69  
    70  	if o.janitorPath == "" {
    71  		return errors.New("required flag --janitor-path was unset")
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  func gatherOptions() options {
    78  	o := options{}
    79  	flag.StringVar(&o.configPath, "config-path", "", "Path to config.yaml.")
    80  	flag.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs.")
    81  	flag.StringVar(&o.janitorPath, "janitor-path", "", "Path to janitor.py.")
    82  	flag.Parse()
    83  	return o
    84  }
    85  
    86  func containers(jb config.JobBase) []v1.Container {
    87  	var containers []v1.Container
    88  	if jb.Spec != nil {
    89  		containers = append(containers, jb.Spec.Containers...)
    90  		containers = append(containers, jb.Spec.InitContainers...)
    91  	}
    92  	if jb.BuildSpec != nil {
    93  		containers = append(containers, jb.BuildSpec.Steps...)
    94  		if jb.BuildSpec.Source != nil && jb.BuildSpec.Source.Custom != nil {
    95  			containers = append(containers, *jb.BuildSpec.Source.Custom)
    96  		}
    97  	}
    98  	return containers
    99  }
   100  
   101  func findProject(jb config.JobBase) (string, int) {
   102  	project := ""
   103  	ttl := defaultTTL
   104  	for _, container := range containers(jb) {
   105  		for _, arg := range container.Args {
   106  			if strings.HasPrefix(arg, "--gcp-project=") {
   107  				project = strings.TrimPrefix(arg, "--gcp-project=")
   108  			}
   109  
   110  			if arg == "--soak" {
   111  				ttl = soakTTL
   112  			}
   113  		}
   114  	}
   115  
   116  	return project, ttl
   117  }
   118  
   119  func clean(proj, janitorPath string, ttl int) error {
   120  	for _, bad := range blocked {
   121  		if bad == proj {
   122  			logrus.Infof("Will skip project %s", proj)
   123  			return nil
   124  		}
   125  	}
   126  
   127  	logrus.Infof("Will clean up %s with ttl %d h", proj, ttl)
   128  
   129  	cmd := exec.Command(janitorPath, fmt.Sprintf("--project=%s", proj), fmt.Sprintf("--hour=%d", ttl))
   130  	b, err := cmd.CombinedOutput()
   131  	if err != nil {
   132  		logrus.WithError(err).Errorf("failed to clean up project %s, error info: %s", proj, string(b))
   133  	} else {
   134  		logrus.Infof("successfully cleaned up project %s", proj)
   135  	}
   136  
   137  	return err
   138  }
   139  
   140  func main() {
   141  	o := gatherOptions()
   142  	if err := o.Validate(); err != nil {
   143  		logrus.Fatalf("Invalid options: %v", err)
   144  	}
   145  
   146  	conf, err := config.Load(o.configPath, o.jobConfigPath)
   147  	if err != nil {
   148  		logrus.WithError(err).Fatal("Error loading config.")
   149  	}
   150  
   151  	failed := []string{}
   152  
   153  	var jobs []config.JobBase
   154  
   155  	for _, v := range conf.AllPresubmits(nil) {
   156  		jobs = append(jobs, v.JobBase)
   157  	}
   158  	for _, v := range conf.AllPostsubmits(nil) {
   159  		jobs = append(jobs, v.JobBase)
   160  	}
   161  	for _, v := range conf.AllPeriodics() {
   162  		jobs = append(jobs, v.JobBase)
   163  	}
   164  
   165  	for _, j := range jobs {
   166  		if project, ttl := findProject(j); project != "" {
   167  			if err := clean(project, o.janitorPath, ttl); err != nil {
   168  				logrus.WithError(err).Errorf("failed to clean %q", project)
   169  				failed = append(failed, project)
   170  			}
   171  		}
   172  	}
   173  
   174  	if len(failed) > 0 {
   175  		logrus.Fatalf("Failed clean %d projects: %v", len(failed), failed)
   176  	}
   177  
   178  	logrus.Info("Successfully cleaned up all projects!")
   179  }