Includes the following fixes: - Fix cron entry format for clean-up script. Crontabs in `/etc` should not have a user field. No idea why it used to work before, but it started giving errors recently: `/bin/sh: root: not found`. - Set required env variable in clean-up script. (Broken in cc6f36a9d.) This was producing the following error: `ERROR: Missing required environment variable 'AIO_CIRCLE_CI_TOKEN'!` - Use the correct path for downloads to be removed. (Broken in cc6f36a9d.) PR Close #25671
		
			
				
	
	
		
			128 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			128 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| // Imports
 | |
| import * as fs from 'fs';
 | |
| import * as path from 'path';
 | |
| import * as shell from 'shelljs';
 | |
| import {HIDDEN_DIR_PREFIX} from '../common/constants';
 | |
| import {GithubApi} from '../common/github-api';
 | |
| import {GithubPullRequests} from '../common/github-pull-requests';
 | |
| import {assertNotMissingOrEmpty, getPrInfoFromDownloadPath, Logger} from '../common/utils';
 | |
| 
 | |
| // Classes
 | |
| export class BuildCleaner {
 | |
| 
 | |
|   private logger = new Logger('BuildCleaner');
 | |
| 
 | |
|   // Constructor
 | |
|   constructor(protected buildsDir: string, protected githubOrg: string, protected githubRepo: string,
 | |
|               protected githubToken: string, protected downloadsDir: string, protected artifactPath: string) {
 | |
|     assertNotMissingOrEmpty('buildsDir', buildsDir);
 | |
|     assertNotMissingOrEmpty('githubOrg', githubOrg);
 | |
|     assertNotMissingOrEmpty('githubRepo', githubRepo);
 | |
|     assertNotMissingOrEmpty('githubToken', githubToken);
 | |
|     assertNotMissingOrEmpty('downloadsDir', downloadsDir);
 | |
|     assertNotMissingOrEmpty('artifactPath', artifactPath);
 | |
|   }
 | |
| 
 | |
|   // Methods - Public
 | |
|   public async cleanUp(): Promise<void> {
 | |
|     try {
 | |
|       this.logger.log('Cleaning up builds and downloads');
 | |
|       const openPrs = await this.getOpenPrNumbers();
 | |
|       this.logger.log(`Open pull requests: ${openPrs.length}`);
 | |
|       await Promise.all([
 | |
|         this.cleanBuilds(openPrs),
 | |
|         this.cleanDownloads(openPrs),
 | |
|       ]);
 | |
|     } catch (error) {
 | |
|       this.logger.error('ERROR:', error);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public async cleanBuilds(openPrs: number[]): Promise<void> {
 | |
|     const existingBuilds = await this.getExistingBuildNumbers();
 | |
|     await this.removeUnnecessaryBuilds(existingBuilds, openPrs);
 | |
|   }
 | |
| 
 | |
|   public async cleanDownloads(openPrs: number[]): Promise<void> {
 | |
|     const existingDownloads = await this.getExistingDownloads();
 | |
|     await this.removeUnnecessaryDownloads(existingDownloads, openPrs);
 | |
|   }
 | |
| 
 | |
|   public getExistingBuildNumbers(): Promise<number[]> {
 | |
|     return new Promise<number[]>((resolve, reject) => {
 | |
|       fs.readdir(this.buildsDir, (err, files) => {
 | |
|         if (err) {
 | |
|           return reject(err);
 | |
|         }
 | |
| 
 | |
|         const buildNumbers = files.
 | |
|           map(name => name.replace(HIDDEN_DIR_PREFIX, '')).   // Remove the "hidden dir" prefix
 | |
|           map(Number).                                        // Convert string to number
 | |
|           filter(Boolean);                                    // Ignore NaN (or 0), because they are not builds
 | |
| 
 | |
|         resolve(buildNumbers);
 | |
|       });
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   public async getOpenPrNumbers(): Promise<number[]> {
 | |
|     const api = new GithubApi(this.githubToken);
 | |
|     const githubPullRequests = new GithubPullRequests(api, this.githubOrg, this.githubRepo);
 | |
|     const prs = await githubPullRequests.fetchAll('open');
 | |
|     return prs.map(pr => pr.number);
 | |
|   }
 | |
| 
 | |
|   public removeDir(dir: string): void {
 | |
|     try {
 | |
|       if (shell.test('-d', dir)) {
 | |
|         shell.chmod('-R', 'a+w', dir);
 | |
|         shell.rm('-rf', dir);
 | |
|       }
 | |
|     } catch (err) {
 | |
|       this.logger.error(`ERROR: Unable to remove '${dir}' due to:`, err);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public removeUnnecessaryBuilds(existingBuildNumbers: number[], openPrNumbers: number[]): void {
 | |
|     const toRemove = existingBuildNumbers.filter(num => !openPrNumbers.includes(num));
 | |
| 
 | |
|     this.logger.log(`Existing builds: ${existingBuildNumbers.length}`);
 | |
|     this.logger.log(`Removing ${toRemove.length} build(s): ${toRemove.join(', ')}`);
 | |
| 
 | |
|     // Try removing public dirs.
 | |
|     toRemove.
 | |
|       map(num => path.join(this.buildsDir, String(num))).
 | |
|       forEach(dir => this.removeDir(dir));
 | |
| 
 | |
|     // Try removing hidden dirs.
 | |
|     toRemove.
 | |
|       map(num => path.join(this.buildsDir, HIDDEN_DIR_PREFIX + String(num))).
 | |
|       forEach(dir => this.removeDir(dir));
 | |
|   }
 | |
| 
 | |
|   public getExistingDownloads(): Promise<string[]> {
 | |
|     const artifactFile = path.basename(this.artifactPath);
 | |
|     return new Promise<string[]>((resolve, reject) => {
 | |
|       fs.readdir(this.downloadsDir, (err, files) => {
 | |
|         if (err) {
 | |
|           return reject(err);
 | |
|         }
 | |
|         files = files.filter(file => file.endsWith(artifactFile));
 | |
|         resolve(files);
 | |
|       });
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   public removeUnnecessaryDownloads(existingDownloads: string[], openPrNumbers: number[]): void {
 | |
|     const toRemove = existingDownloads.filter(filePath => {
 | |
|       const {pr} = getPrInfoFromDownloadPath(filePath);
 | |
|       return !openPrNumbers.includes(pr);
 | |
|     });
 | |
| 
 | |
|     this.logger.log(`Existing downloads: ${existingDownloads.length}`);
 | |
|     this.logger.log(`Removing ${toRemove.length} download(s): ${toRemove.join(', ')}`);
 | |
| 
 | |
|     toRemove.forEach(filePath => shell.rm(path.join(this.downloadsDir, filePath)));
 | |
|   }
 | |
| }
 |