func configureTeams(client teamClient, orgName string, orgConfig org.Config, maxDelta float64, ignoreSecretTeams bool) (map[string]github.Team, error) {
	if err := validateTeamNames(orgConfig); err != nil {
		return nil, err
	}

	// What teams exist?
	ids := map[int]github.Team{}
	ints := sets.Int{}
	teamList, err := client.ListTeams(orgName)
	if err != nil {
		return nil, fmt.Errorf("failed to list teams: %v", err)
	}
	logrus.Debugf("Found %d teams", len(teamList))
	for _, t := range teamList {
		if ignoreSecretTeams && org.Privacy(t.Privacy) == org.Secret {
			continue
		}
		ids[t.ID] = t
		ints.Insert(t.ID)
	}
	if ignoreSecretTeams {
		logrus.Debugf("Found %d non-secret teams", len(teamList))
	}

	// What is the lowest ID for each team?
	older := map[string][]github.Team{}
	names := map[string]github.Team{}
	for _, t := range ids {
		logger := logrus.WithFields(logrus.Fields{"id": t.ID, "name": t.Name})
		n := t.Name
		switch val, ok := names[n]; {
		case !ok: // first occurrence of the name
			logger.Debug("First occurrence of this team name.")
			names[n] = t
		case ok && t.ID < val.ID: // t has the lower ID, replace and send current to older set
			logger.Debugf("Replacing previous recorded team (%d) with this one due to smaller ID.", val.ID)
			names[n] = t
			older[n] = append(older[n], val)
		default: // t does not have smallest id, add it to older set
			logger.Debugf("Adding team to older set as a smaller ID is already recoded for it.", val.ID)
			older[n] = append(older[n], val)
		}
	}

	// What team are we using for each configured name, and which names are missing?
	matches := map[string]github.Team{}
	missing := map[string]org.Team{}
	used := sets.Int{}
	var match func(teams map[string]org.Team)
	match = func(teams map[string]org.Team) {
		for name, orgTeam := range teams {
			logger := logrus.WithField("name", name)
			match(orgTeam.Children)
			t := findTeam(names, name, orgTeam.Previously...)
			if t == nil {
				missing[name] = orgTeam
				logger.Debug("Could not find team in GitHub for this configuration.")
				continue
			}
			matches[name] = *t // t.Name != name if we matched on orgTeam.Previously
			logger.WithField("id", t.ID).Debug("Found a team in GitHub for this configuration.")
			used.Insert(t.ID)
		}
	}
	match(orgConfig.Teams)

	// First compute teams we will delete, ensure we are not deleting too many
	unused := ints.Difference(used)
	if delta := float64(len(unused)) / float64(len(ints)); delta > maxDelta {
		return nil, fmt.Errorf("cannot delete %d teams or %.3f of %s teams (exceeds limit of %.3f)", len(unused), delta, orgName, maxDelta)
	}

	// Create any missing team names
	var failures []string
	for name, orgTeam := range missing {
		t := &github.Team{Name: name}
		if orgTeam.Description != nil {
			t.Description = *orgTeam.Description
		}
		if orgTeam.Privacy != nil {
			t.Privacy = string(*orgTeam.Privacy)
		}
		t, err := client.CreateTeam(orgName, *t)
		if err != nil {
			logrus.WithError(err).Warnf("Failed to create %s in %s", name, orgName)
			failures = append(failures, name)
			continue
		}
		matches[name] = *t
		// t.ID may include an ID already present in ints if other actors are deleting teams.
		used.Insert(t.ID)
	}
	if n := len(failures); n > 0 {
		return nil, fmt.Errorf("failed to create %d teams: %s", n, strings.Join(failures, ", "))
	}

	// Remove any IDs returned by CreateTeam() that are in the unused set.
	if reused := unused.Intersection(used); len(reused) > 0 {
		// Logically possible for:
		// * another actor to delete team N after the ListTeams() call
		// * github to reuse team N after someone deleted it
		// Therefore used may now include IDs in unused, handle this situation.
		logrus.Warnf("Will not delete %d team IDs reused by github: %v", len(reused), reused.List())
		unused = unused.Difference(reused)
	}
	// Delete undeclared teams.
	for id := range unused {
		if err := client.DeleteTeam(id); err != nil {
			str := fmt.Sprintf("%d(%s)", id, ids[id].Name)
			logrus.WithError(err).Warnf("Failed to delete team %s from %s", str, orgName)
			failures = append(failures, str)
		}
	}
	if n := len(failures); n > 0 {
		return nil, fmt.Errorf("failed to delete %d teams: %s", n, strings.Join(failures, ", "))
	}

	// Return matches
	return matches, nil
}
func configureTeams(client teamClient, orgName string, orgConfig org.Config, maxDelta float64, ignoreSecretTeams bool) (map[string]github.Team, error) {
	if err := validateTeamNames(orgConfig); err != nil {
		return nil, err
	}

	// What teams exist?
	ids := map[int]github.Team{}
	ints := sets.Int{}
	teamList, err := client.ListTeams(orgName)
	if err != nil {
		return nil, fmt.Errorf("failed to list teams: %v", err)
	}
	logrus.Debugf("Found %d teams", len(teamList))
	for _, t := range teamList {
		if ignoreSecretTeams && org.Privacy(t.Privacy) == org.Secret {
			continue
		}
		ids[t.ID] = t
		ints.Insert(t.ID)
	}
	if ignoreSecretTeams {
		logrus.Debugf("Found %d non-secret teams", len(teamList))
	}

	// What is the lowest ID for each team?
	older := map[string][]github.Team{}
	names := map[string]github.Team{}
	for _, t := range ids {
		logger := logrus.WithFields(logrus.Fields{"id": t.ID, "name": t.Name})
		n := t.Name
		switch val, ok := names[n]; {
		case !ok: // first occurrence of the name
			logger.Debug("First occurrence of this team name.")
			names[n] = t
		case ok && t.ID < val.ID: // t has the lower ID, replace and send current to older set
			logger.Debugf("Replacing previous recorded team (%d) with this one due to smaller ID.", val.ID)
			names[n] = t
			older[n] = append(older[n], val)
		default: // t does not have smallest id, add it to older set
			logger.Debugf("Adding team to older set as a smaller ID is already recoded for it.", val.ID)
			older[n] = append(older[n], val)
		}
	}

	// What team are we using for each configured name, and which names are missing?
	matches := map[string]github.Team{}
	missing := map[string]org.Team{}
	used := sets.Int{}
	var match func(teams map[string]org.Team)
	match = func(teams map[string]org.Team) {
		for name, orgTeam := range teams {
			logger := logrus.WithField("name", name)
			match(orgTeam.Children)
			t := findTeam(names, name, orgTeam.Previously...)
			if t == nil {
				missing[name] = orgTeam
				logger.Debug("Could not find team in GitHub for this configuration.")
				continue
			}
			matches[name] = *t // t.Name != name if we matched on orgTeam.Previously
			logger.WithField("id", t.ID).Debug("Found a team in GitHub for this configuration.")
			used.Insert(t.ID)
		}
	}
	match(orgConfig.Teams)

	// First compute teams we will delete, ensure we are not deleting too many
	unused := ints.Difference(used)
	if delta := float64(len(unused)) / float64(len(ints)); delta > maxDelta {
		return nil, fmt.Errorf("cannot delete %d teams or %.3f of %s teams (exceeds limit of %.3f)", len(unused), delta, orgName, maxDelta)
	}

	// Create any missing team names
	var failures []string
	for name, orgTeam := range missing {
		t := &github.Team{Name: name}
		if orgTeam.Description != nil {
			t.Description = *orgTeam.Description
		}
		if orgTeam.Privacy != nil {
			t.Privacy = string(*orgTeam.Privacy)
		}
		t, err := client.CreateTeam(orgName, *t)
		if err != nil {
			logrus.WithError(err).Warnf("Failed to create %s in %s", name, orgName)
			failures = append(failures, name)
			continue
		}
		matches[name] = *t
		// t.ID may include an ID already present in ints if other actors are deleting teams.
		used.Insert(t.ID)
	}
	if n := len(failures); n > 0 {
		return nil, fmt.Errorf("failed to create %d teams: %s", n, strings.Join(failures, ", "))
	}

	// Remove any IDs returned by CreateTeam() that are in the unused set.
	if reused := unused.Intersection(used); len(reused) > 0 {
		// Logically possible for:
		// * another actor to delete team N after the ListTeams() call
		// * github to reuse team N after someone deleted it
		// Therefore used may now include IDs in unused, handle this situation.
		logrus.Warnf("Will not delete %d team IDs reused by github: %v", len(reused), reused.List())
		unused = unused.Difference(reused)
	}
	// Delete undeclared teams.
	for id := range unused {
		if err := client.DeleteTeam(id); err != nil {
			str := fmt.Sprintf("%d(%s)", id, ids[id].Name)
			logrus.WithError(err).Warnf("Failed to delete team %s from %s", str, orgName)
			failures = append(failures, str)
		}
	}
	if n := len(failures); n > 0 {
		return nil, fmt.Errorf("failed to delete %d teams: %s", n, strings.Join(failures, ", "))
	}

	// Return matches
	return matches, nil
}
// configureTeams returns the ids for all expected team names, creating/deleting teams as necessary.