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)
}
}
}
}