label_sync/main.go
func main() {
	logrus.SetFormatter(
		logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "label_sync"}),
	)

	flag.Parse()
	if *debug {
		logrus.SetLevel(logrus.DebugLevel)
	}

	config, err := LoadConfig(*labelsPath, *orgs)
	if err != nil {
		logrus.WithError(err).Fatalf("failed to load --config=%s", *labelsPath)
	}

	if *onlyRepos != "" && *skipRepos != "" {
		logrus.Fatalf("--only and --skip cannot both be set")
	}

	if *onlyRepos != "" && *orgs != "" {
		logrus.Fatalf("--only and --orgs cannot both be set")
	}

	switch {
	case *action == "docs":
		if err := writeDocs(*docsTemplate, *docsOutput, *config); err != nil {
			logrus.WithError(err).Fatalf("failed to write docs using docs-template %s to docs-output %s", *docsTemplate, *docsOutput)
		}
	case *action == "css":
		if err := writeCSS(*cssTemplate, *cssOutput, *config); err != nil {
			logrus.WithError(err).Fatalf("failed to write css file using css-template %s to css-output %s", *cssTemplate, *cssOutput)
		}
	case *action == "sync":
		githubClient, err := newClient(*token, *tokens, *tokenBurst, !*confirm, *graphqlEndpoint, endpoint.Strings()...)
		if err != nil {
			logrus.WithError(err).Fatal("failed to create client")
		}

		// there are three ways to configure which repos to sync:
		//  - a whitelist of org/repo values
		//  - a list of orgs for which we sync all repos
		//  - a list of orgs with a blacklist of org/repo values
		if *onlyRepos != "" {
			reposToSync, parseError := parseCommaDelimitedList(*onlyRepos)
			if parseError != nil {
				logrus.WithError(err).Fatal("invalid value for --only")
			}
			for org := range reposToSync {
				if err = syncOrg(org, githubClient, *config, reposToSync[org]); err != nil {
					logrus.WithError(err).Fatalf("failed to update %s", org)
				}
			}
			return
		}

		skippedRepos := map[string][]string{}
		if *skipRepos != "" {
			reposToSkip, parseError := parseCommaDelimitedList(*skipRepos)
			if parseError != nil {
				logrus.WithError(err).Fatal("invalid value for --skip")
			}
			skippedRepos = reposToSkip
		}

		for _, org := range strings.Split(*orgs, ",") {
			org = strings.TrimSpace(org)
			logger := logrus.WithField("org", org)
			logger.Info("Reading repos")
			repos, err := loadRepos(org, githubClient)
			if err != nil {
				logger.WithError(err).Fatalf("failed to read repos")
			}
			if skipped, exist := skippedRepos[org]; exist {
				repos = sets.NewString(repos...).Difference(sets.NewString(skipped...)).UnsortedList()
			}
			if err = syncOrg(org, githubClient, *config, repos); err != nil {
				logrus.WithError(err).Fatalf("failed to update %s", org)
			}
		}
	default:
		logrus.Fatalf("unrecognized action: %s", *action)
	}
}
func main() {
	logrus.SetFormatter(
		logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "label_sync"}),
	)

	flag.Parse()
	if *debug {
		logrus.SetLevel(logrus.DebugLevel)
	}

	config, err := LoadConfig(*labelsPath, *orgs)
	if err != nil {
		logrus.WithError(err).Fatalf("failed to load --config=%s", *labelsPath)
	}

	if *onlyRepos != "" && *skipRepos != "" {
		logrus.Fatalf("--only and --skip cannot both be set")
	}

	if *onlyRepos != "" && *orgs != "" {
		logrus.Fatalf("--only and --orgs cannot both be set")
	}

	switch {
	case *action == "docs":
		if err := writeDocs(*docsTemplate, *docsOutput, *config); err != nil {
			logrus.WithError(err).Fatalf("failed to write docs using docs-template %s to docs-output %s", *docsTemplate, *docsOutput)
		}
	case *action == "css":
		if err := writeCSS(*cssTemplate, *cssOutput, *config); err != nil {
			logrus.WithError(err).Fatalf("failed to write css file using css-template %s to css-output %s", *cssTemplate, *cssOutput)
		}
	case *action == "sync":
		githubClient, err := newClient(*token, *tokens, *tokenBurst, !*confirm, *graphqlEndpoint, endpoint.Strings()...)
		if err != nil {
			logrus.WithError(err).Fatal("failed to create client")
		}

		// there are three ways to configure which repos to sync:
		//  - a whitelist of org/repo values
		//  - a list of orgs for which we sync all repos
		//  - a list of orgs with a blacklist of org/repo values
		if *onlyRepos != "" {
			reposToSync, parseError := parseCommaDelimitedList(*onlyRepos)
			if parseError != nil {
				logrus.WithError(err).Fatal("invalid value for --only")
			}
			for org := range reposToSync {
				if err = syncOrg(org, githubClient, *config, reposToSync[org]); err != nil {
					logrus.WithError(err).Fatalf("failed to update %s", org)
				}
			}
			return
		}

		skippedRepos := map[string][]string{}
		if *skipRepos != "" {
			reposToSkip, parseError := parseCommaDelimitedList(*skipRepos)
			if parseError != nil {
				logrus.WithError(err).Fatal("invalid value for --skip")
			}
			skippedRepos = reposToSkip
		}

		for _, org := range strings.Split(*orgs, ",") {
			org = strings.TrimSpace(org)
			logger := logrus.WithField("org", org)
			logger.Info("Reading repos")
			repos, err := loadRepos(org, githubClient)
			if err != nil {
				logger.WithError(err).Fatalf("failed to read repos")
			}
			if skipped, exist := skippedRepos[org]; exist {
				repos = sets.NewString(repos...).Difference(sets.NewString(skipped...)).UnsortedList()
			}
			if err = syncOrg(org, githubClient, *config, repos); err != nil {
				logrus.WithError(err).Fatalf("failed to update %s", org)
			}
		}
	default:
		logrus.Fatalf("unrecognized action: %s", *action)
	}
}
// Main function
// Typical run with production configuration should require no parameters
// It expects:
// "labels" file in "/etc/config/labels.yaml"
// github OAuth2 token in "/etc/github/oauth", this token must have write access to all org's repos
// It uses request retrying (in case of run out of GH API points)
// It took about 10 minutes to process all my 8 repos with all wanted "kubernetes" labels (70+)
// Next run takes about 22 seconds to check if all labels are correct on all repos