github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/gits/features/disable_features.go (about)

     1  package features
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/jenkins-x/jx-logging/pkg/log"
     9  
    10  	"github.com/olli-ai/jx/v2/pkg/gits"
    11  	"github.com/olli-ai/jx/v2/pkg/util"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // DisableFeaturesForOrg iterates over all the repositories in org (except those that match excludes) and disables issue
    16  // trackers, projects and wikis if they are not in use.
    17  //
    18  // Issue trackers are not in use if they have no open or closed issues
    19  // Projects are not in use if there are no open projects
    20  // Wikis are not in use if the provider returns that the wiki is not enabled
    21  //
    22  // Note that the requirement for issues is no issues at all so that we don't close issue trackers that have historic info
    23  //
    24  // If includes is not empty only those that match an include will be operated on. If dryRun is true, the operations to
    25  // be done will printed and but nothing done. If batchMode is false, then each change will be prompted.
    26  func DisableFeaturesForOrg(org string, includes []string, excludes []string, dryRun bool, batchMode bool, provider gits.GitProvider, handles util.IOFileHandles) error {
    27  
    28  	type closeTypes struct {
    29  		*gits.GitRepository
    30  		closeIssues  bool
    31  		closeProject bool
    32  		closeWiki    bool
    33  		keepIssues   bool
    34  		keepProject  bool
    35  		keepWiki     bool
    36  	}
    37  
    38  	repos, err := provider.ListRepositories(org)
    39  	if err != nil {
    40  		return errors.Wrapf(err, "listing repositories in %s", org)
    41  	}
    42  	sort.Slice(repos, func(i, j int) bool {
    43  		return repos[i].Name < repos[j].Name
    44  	})
    45  	// dedupe
    46  	dedupedRepos := make([]*gits.GitRepository, 0)
    47  	previous := ""
    48  	for _, r := range repos {
    49  		if r.Name != previous {
    50  			dedupedRepos = append(dedupedRepos, r)
    51  		}
    52  		previous = r.Name
    53  	}
    54  	ct := make([]closeTypes, 0)
    55  
    56  	log.Logger().Infof("Analysing repositories\n")
    57  
    58  	for _, repo := range dedupedRepos {
    59  		c := closeTypes{
    60  			GitRepository: repo,
    61  			closeIssues:   false,
    62  			closeProject:  false,
    63  			closeWiki:     false,
    64  		}
    65  		if c.Archived {
    66  			// Ignore archived repos
    67  			continue
    68  		}
    69  		if !util.Contains(excludes, fmt.Sprintf("%s/%s", repo.Organisation, repo.Name)) && (len(includes) == 0 || util.Contains(includes, fmt.Sprintf("%s/%s", repo.Organisation, repo.Name))) {
    70  			issues := ""
    71  			if repo.HasIssues {
    72  				openIssues, err := provider.SearchIssues(repo.Organisation, repo.Name, "open")
    73  				if err != nil {
    74  					return errors.Wrapf(err, "finding open issues in %s/%s", repo.Organisation, repo.Name)
    75  				}
    76  				closedIssues, err := provider.SearchIssues(repo.Organisation, repo.Name, "closed")
    77  				if err != nil {
    78  					return errors.Wrapf(err, "finding open issues in %s/%s", repo.Organisation, repo.Name)
    79  				}
    80  				open := len(openIssues)
    81  				all := len(openIssues) + len(closedIssues)
    82  				stat := fmt.Sprintf("%d/%d", open, all)
    83  				if open > 0 {
    84  					stat = util.ColorInfo(stat)
    85  				}
    86  				if all == 0 {
    87  					c.closeIssues = true
    88  				} else {
    89  					c.keepIssues = true
    90  				}
    91  				issues = fmt.Sprintf("%s issues are open", stat)
    92  			} else {
    93  				issues = "Disabled"
    94  			}
    95  			projects := ""
    96  			if repo.HasProjects {
    97  				allProjects, err := provider.GetProjects(repo.Organisation, repo.Name)
    98  				if err != nil {
    99  					return errors.Wrapf(err, "getting projects for %s/%s", repo.Organisation, repo.Name)
   100  				}
   101  				open := 0
   102  				for _, p := range allProjects {
   103  					if p.State == gits.ProjectOpen {
   104  						open++
   105  					}
   106  				}
   107  				stat := fmt.Sprintf("%d/%d", open, len(allProjects))
   108  				if open > 0 {
   109  					stat = util.ColorInfo(stat)
   110  					c.keepProject = true
   111  				} else {
   112  					c.closeProject = true
   113  				}
   114  				projects = fmt.Sprintf("%s open projects", stat)
   115  			} else {
   116  				projects = "Disabled"
   117  			}
   118  			wikis := ""
   119  			if repo.HasWiki {
   120  				enabled, err := provider.IsWikiEnabled(repo.Organisation, repo.Name)
   121  				if err != nil {
   122  					return errors.Wrapf(err, "checking if wiki for %s/%s is enabled", repo.Organisation, repo.Name)
   123  				}
   124  				if enabled {
   125  					wikis = util.ColorInfo("In use")
   126  					c.keepWiki = true
   127  				} else {
   128  					wikis = "Not in use"
   129  					c.closeWiki = true
   130  				}
   131  			} else {
   132  				wikis = "Disabled"
   133  			}
   134  			log.Logger().Infof(`
   135  %s
   136  Issues: %s
   137  Projects: %s
   138  Wiki Pages: %s
   139  `, util.ColorBold(fmt.Sprintf("%s/%s", repo.Organisation, repo.Name)), issues, projects, wikis)
   140  			ct = append(ct, c)
   141  		}
   142  	}
   143  	log.Logger().Infof("\n\n Analysis complete\n")
   144  	for _, c := range ct {
   145  		toClose := make([]string, 0)
   146  		toKeep := make([]string, 0)
   147  		var wiki, issues, project *bool
   148  		disabled := false
   149  		if c.closeProject {
   150  			toClose = append(toClose, util.ColorWarning("project"))
   151  			project = &disabled
   152  		}
   153  		if c.keepProject {
   154  			toKeep = append(toKeep, util.ColorInfo("project"))
   155  		}
   156  		if c.closeWiki {
   157  			toClose = append(toClose, util.ColorWarning("wiki"))
   158  			wiki = &disabled
   159  		}
   160  		if c.keepWiki {
   161  			toKeep = append(toKeep, util.ColorInfo("wiki"))
   162  		}
   163  		if c.closeIssues {
   164  			toClose = append(toClose, util.ColorWarning("issues"))
   165  			issues = &disabled
   166  		}
   167  		if c.keepIssues {
   168  			toKeep = append(toKeep, util.ColorInfo("issues"))
   169  		}
   170  		if len(toClose) > 0 || len(toKeep) > 0 {
   171  			if dryRun {
   172  				log.Logger().Infof("Would disable %s on %s", strings.Join(toClose, ", "), util.ColorInfo(fmt.Sprintf("%s/%s", c.Organisation, c.Name)))
   173  			} else {
   174  
   175  				if !batchMode {
   176  					if answer, err := util.Confirm(fmt.Sprintf("Are you sure you want to disable %s on %s", strings.Join(toClose, ","), util.ColorInfo(fmt.Sprintf("%s/%s", c.Organisation, c.Name))), true, "", handles); err != nil {
   177  						return err
   178  					} else if !answer {
   179  						continue
   180  					}
   181  				}
   182  				logStr := ""
   183  				if len(toClose) > 0 {
   184  					logStr = fmt.Sprintf("Disabling %s", strings.Join(toClose, ", "))
   185  				}
   186  				if len(toKeep) > 0 {
   187  					if len(logStr) > 0 {
   188  						logStr += "; "
   189  					}
   190  					logStr += fmt.Sprintf("Keeping %s", strings.Join(toKeep, ", "))
   191  				}
   192  				if len(logStr) > 0 {
   193  					log.Logger().Infof("%s: %s", util.ColorInfo(fmt.Sprintf("%s/%s", c.Organisation, c.Name)), logStr)
   194  				}
   195  				_, err = provider.ConfigureFeatures(c.Organisation, c.Name, issues, project, wiki)
   196  				if err != nil {
   197  					return errors.Wrapf(err, "disabling %s on %s/%s", strings.Join(toClose, ", "), c.Organisation, c.Name)
   198  				}
   199  			}
   200  		}
   201  	}
   202  	return nil
   203  }