func main()

in autoroll/go/autoroll-pusher/main.go [319:560]


func main() {
	common.Init()

	ctx := context.Background()

	// Validate flags.
	if !*updateRollerConfig && !*updateBeImage && !*updateFeImage {
		log.Fatal("One or more of --update-config, --update-be-image, or --update-fe-image is required.")
	}
	if flagWasSet("roller") && *rollerRe == "" {
		// This is almost certainly a mistake.
		log.Fatal("--roller was set to an empty string.")
	}
	if *commitMsg == "" {
		r := bufio.NewReader(os.Stdin)
		fmt.Println("--commit-msg was not provided. Please enter a commit message, followed by EOF (ctrl+D):")
		msg, err := ioutil.ReadAll(r)
		if err != nil {
			log.Fatalf("Failed to read commit message from stdin: %s", err)
		}
		*commitMsg = string(msg)
	}

	// Derive paths to config files.
	_, thisFileName, _, ok := runtime.Caller(0)
	if !ok {
		log.Fatal("Unable to find path to current file.")
	}
	autorollDir := filepath.Dir(filepath.Dir(filepath.Dir(thisFileName)))

	// Determine where to look for roller configs.
	var rollerRegex *regexp.Regexp
	if *rollerRe != "" {
		var err error
		rollerRegex, err = regexp.Compile(*rollerRe)
		if err != nil {
			log.Fatalf("Invalid regex for --roller: %s", err)
		}
	}
	// TODO(borenet): We should use the go/kube/clusterconfig package.
	clusters := []*clusterCfg{
		{
			FeConfigFile: filepath.Join(autorollDir, "go", "autoroll-fe", "cfg-public.json"),
			Project:      PROJECT_PUBLIC,
			Name:         "skia-public",
		},
		{
			FeConfigFile: filepath.Join(autorollDir, "go", "autoroll-fe", "cfg-corp.json"),
			Project:      PROJECT_CORP,
			Name:         "skia-corp",
		},
	}

	// Ensure that the config repo is present.
	configCo := &git.Checkout{GitDir: git.GitDir(CONFIG_REPO_LOCATION)}
	if _, err := os.Stat(configCo.Dir()); os.IsNotExist(err) {
		if err := git.Clone(ctx, CONFIG_REPO_URL, configCo.Dir(), false); err != nil {
			log.Fatalf("Failed to clone config repo: %s", err)
		}
	}
	if err := configCo.Fetch(ctx); err != nil {
		log.Fatal(err)
	}
	configRepoIsDirty, configRepoStatus, err := configCo.IsDirty(ctx)
	if err != nil {
		log.Fatal(err)
	} else if configRepoIsDirty {
		fmt.Println(configRepoStatus)
		fmt.Println(fmt.Sprintf("Checkout in %s is dirty; are you sure you want to use it? (y/n): ", configCo.Dir()))
		reader := bufio.NewReader(os.Stdin)
		read, err := reader.ReadString('\n')
		if err != nil {
			log.Fatal(err)
		}
		read = strings.TrimSpace(read)
		if read != "y" {
			os.Exit(1)
		}
	}

	// Load all configs. This a nested map whose keys are config dir paths,
	// sub-map keys are config file names, and values are roller configs.
	configs := map[*clusterCfg]map[string]*config.Config{}
	for _, cluster := range clusters {
		dirEntries, err := ioutil.ReadDir(filepath.Join(configCo.Dir(), cluster.Name))
		if err != nil {
			log.Fatalf("Failed to read roller configs in %s: %s", cluster, err)
		}
		cfgsInDir := make(map[string]*config.Config, len(dirEntries))
		for _, entry := range dirEntries {
			if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".cfg") {
				cfgPath := filepath.Join(configCo.Dir(), cluster.Name, entry.Name())
				cfgBytes, err := ioutil.ReadFile(cfgPath)
				if err != nil {
					log.Fatalf("Failed to read roller config %s: %s", cfgPath, err)
				}
				var cfg config.Config
				if err := prototext.Unmarshal(cfgBytes, &cfg); err != nil {
					log.Fatalf("Failed to parse roller config: %s", err)
				}
				if rollerRegex == nil || rollerRegex.MatchString(cfg.RollerName) {
					if err := cfg.Validate(); err != nil {
						log.Fatalf("%s is invalid: %s", cfgPath, err)
					}
					cfgsInDir[filepath.Base(entry.Name())] = &cfg
				}
			}
		}
		if len(cfgsInDir) == 0 {
			fmt.Println(fmt.Sprintf("No matching rollers in %s. Skipping.", cluster.Name))
		} else {
			configs[cluster] = cfgsInDir
		}
	}
	if len(configs) == 0 {
		log.Fatalf("Found no rollers matching %q", *rollerRe)
	}

	// Update the backend image if requested.
	if *updateBeImage {
		latestImageBe, err := getLatestImage(GCR_IMAGE_BE)
		if err != nil {
			log.Fatalf("Failed to get latest image for %s: %s", GCR_IMAGE_BE, err)
		}
		imageBytes := []byte(fmt.Sprintf("  image:  %q", latestImageBe))
		for cluster, byCluster := range configs {
			for filename, config := range byCluster {
				// Update the config files on disk.
				cfgPath := filepath.Join(configCo.Dir(), cluster.Name, filename)
				cfgBytes, err := ioutil.ReadFile(cfgPath)
				if err != nil {
					log.Fatalf("Failed to read roller config %s: %s", cfgPath, err)
				}
				cfgBytes = dockerImageRe.ReplaceAll(cfgBytes, imageBytes)
				if err := ioutil.WriteFile(cfgPath, cfgBytes, os.ModePerm); err != nil {
					log.Fatalf("Failed to write roller config %s: %s", cfgPath, err)
				}

				// Update the configs we already read.
				config.Kubernetes.Image = latestImageBe
			}
		}
		// If the repo is not dirty, commit and push the updated configs.
		if !configRepoIsDirty {
			msg := *commitMsg + "\n\n" + rubberstamper.RandomChangeID()
			if _, err := configCo.Git(ctx, "commit", "-a", "-m", msg); err != nil {
				log.Fatalf("Failed to 'git commit' k8s config file(s): %s", err)
			}
			if _, err := configCo.Git(ctx, "push", git.DefaultRemote, rubberstamper.PushRequestAutoSubmit); err != nil {
				// The upstream might have changed while we were
				// working. Rebase and try again.
				if err2 := configCo.Fetch(ctx); err2 != nil {
					log.Fatalf("Failed to push with %q and failed to fetch with %q", err, err2)
				}
				if _, err2 := configCo.Git(ctx, "rebase"); err2 != nil {
					log.Fatalf("Failed to push with %q and failed to rebase with %q", err, err2)
				}
			}
			// Remove the commit we just made, so that we aren't leaving the
			// checkout in a "dirty" state.
			if _, err := configCo.Git(ctx, "reset", "--hard", git.DefaultRemoteBranch); err != nil {
				log.Fatalf("Failed to cleanup: %s", err)
			}
		} else {
			fmt.Println(fmt.Sprintf("Updated config files in %s but not committing because of dirty checkout.", configCo.Dir()))
		}
	}

	// Create the checkout.
	co, err := git.NewTempCheckout(ctx, K8S_CONFIG_REPO)
	if err != nil {
		log.Fatalf("Failed to create temporary checkout: %s", err)
	}
	defer co.Delete()

	// Update the configs.
	modByCluster := make(map[*clusterCfg][]string, len(configs))
	for cluster, cfgs := range configs {
		modified, err := updateConfigs(ctx, co.Checkout, cluster, cfgs)
		if err != nil {
			log.Fatalf("Failed to update configs: %s", err)
		}
		if len(modified) > 0 {
			modFullPaths := make([]string, 0, len(modified))
			fmt.Println(fmt.Sprintf("Modified the following files in %s:", cluster.Name))
			for _, f := range modified {
				fmt.Println(fmt.Sprintf("  %s", f))
				modFullPaths = append(modFullPaths, filepath.Join(cluster.Name, f))
			}
			modByCluster[cluster] = modFullPaths
		} else {
			fmt.Fprintf(os.Stderr, "No configs modified in %s.\n", cluster.Name)
		}
	}

	// TODO(borenet): Support rolling back on error.
	for cluster, modified := range modByCluster {
		kubecfg, cleanup, err := switchCluster(ctx, cluster.Project)
		if err != nil {
			log.Fatalf("Failed to update k8s configs: %s", err)
		}
		defer cleanup()
		args := []string{"apply"}
		for _, f := range modified {
			args = append(args, "-f", f)
		}
		if _, err := exec.RunCommand(ctx, &exec.Command{
			Name:        "kubectl",
			Args:        args,
			Dir:         co.Dir(),
			Env:         []string{fmt.Sprintf("KUBECONFIG=%s", kubecfg)},
			InheritEnv:  true,
			InheritPath: true,
		}); err != nil {
			log.Fatalf("Failed to apply k8s config file(s) in %s: %s", co.Dir(), err)
		}
	}

	// Commit and push the modified configs.
	if *commitMsg != "" {
		for _, modified := range modByCluster {
			cmd := append([]string{"add"}, modified...)
			if _, err := co.Git(ctx, cmd...); err != nil {
				log.Fatalf("Failed to 'git add' k8s config file(s): %s", err)
			}
		}
		msg := *commitMsg + "\n\n" + rubberstamper.RandomChangeID()
		if _, err := co.Git(ctx, "commit", "-m", msg); err != nil {
			log.Fatalf("Failed to 'git commit' k8s config file(s): %s", err)
		}
		if _, err := co.Git(ctx, "push", git.DefaultRemote, rubberstamper.PushRequestAutoSubmit); err != nil {
			// The upstream might have changed while we were
			// working. Rebase and try again.
			if err2 := co.Fetch(ctx); err2 != nil {
				log.Fatalf("Failed to push with %q and failed to fetch with %q", err, err2)
			}
			if _, err2 := co.Git(ctx, "rebase"); err2 != nil {
				log.Fatalf("Failed to push with %q and failed to rebase with %q", err, err2)
			}
		}
	}
}