public override async Task ExecuteAsync() { _repoNameFilterRegex = new Regex(ManifestFilter.GetFilterRegexPattern(Options.RepoName)); _loggerService.WriteHeading("FINDING IMAGES TO CLEAN"); _loggerService.WriteSubheading($"Connecting to ACR '{Options.RegistryName}'"); using IAcrClient acrClient = await _acrClientFactory.CreateAsync( Options.RegistryName, Options.ServicePrincipal.Tenant, Options.ServicePrincipal.ClientId, Options.ServicePrincipal.Secret); _loggerService.WriteSubheading($"Querying catalog of ACR '{Options.RegistryName}'"); Catalog catalog = await acrClient.GetCatalogAsync(); _loggerService.WriteHeading("DELETING IMAGES"); List <string> deletedRepos = new List <string>(); List <string> deletedImages = new List <string>(); IEnumerable <Task> cleanupTasks = catalog.RepositoryNames .Where(repoName => _repoNameFilterRegex.IsMatch(repoName)) .Select(repoName => acrClient.GetRepositoryAsync(repoName)) .Select(getRepoTask => ProcessRepoAsync(acrClient, getRepoTask, deletedRepos, deletedImages)) .ToArray(); await Task.WhenAll(cleanupTasks); await LogSummaryAsync(acrClient, deletedRepos, deletedImages); }
private static async Task <ManifestInfo?> GetSubscriptionManifestAsync(Subscription subscription, ManifestFilterOptions filterOptions, HttpClient httpClient, ILoggerService loggerService, Action <ManifestOptions>?configureOptions) { // If the command is filtered with an OS type that does not match the OsType filter of the subscription, // then there are no images that need to be inspected. string osTypeRegexPattern = ManifestFilter.GetFilterRegexPattern(filterOptions.OsType); if (!string.IsNullOrEmpty(subscription.OsType) && !Regex.IsMatch(subscription.OsType, osTypeRegexPattern, RegexOptions.IgnoreCase)) { return(null); } string repoPath = await GitHelper.DownloadAndExtractGitRepoArchiveAsync(httpClient, subscription.Manifest, loggerService); try { TempManifestOptions manifestOptions = new(filterOptions) { Manifest = Path.Combine(repoPath, subscription.Manifest.Path), Variables = subscription.Manifest.Variables }; configureOptions?.Invoke(manifestOptions); return(ManifestInfo.Load(manifestOptions)); } finally { // The path to the repo is stored inside a zip extraction folder so be sure to delete that // zip extraction folder, not just the inner repo folder. Directory.Delete(new DirectoryInfo(repoPath).Parent !.FullName, true); } }
private async Task <IEnumerable <string> > GetPathsToRebuildAsync(Subscription subscription) { // If the command is filtered with an OS type that does not match the OsType filter of the subscription, // then there are no images that need to be inspected. string osTypeRegexPattern = ManifestFilter.GetFilterRegexPattern(Options.FilterOptions.OsType); if (!string.IsNullOrEmpty(subscription.OsType) && !Regex.IsMatch(subscription.OsType, osTypeRegexPattern, RegexOptions.IgnoreCase)) { return(Enumerable.Empty <string>()); } _loggerService.WriteMessage($"Processing subscription: {subscription.Id}"); string repoPath = await GetGitRepoPath(subscription); TempManifestOptions manifestOptions = new TempManifestOptions(Options.FilterOptions) { Manifest = Path.Combine(repoPath, subscription.Manifest.Path) }; ManifestInfo manifest = ManifestInfo.Load(manifestOptions); ImageArtifactDetails imageArtifactDetails = await GetImageInfoForSubscriptionAsync(subscription, manifest); List <string> pathsToRebuild = new List <string>(); IEnumerable <PlatformInfo> allPlatforms = manifest.GetAllPlatforms().ToList(); foreach (RepoInfo repo in manifest.FilteredRepos) { IEnumerable <PlatformInfo> platforms = repo.FilteredImages .SelectMany(image => image.FilteredPlatforms) .Where(platform => !platform.IsInternalFromImage(platform.FinalStageFromImage)); RepoData repoData = imageArtifactDetails.Repos .FirstOrDefault(s => s.Repo == repo.Name); foreach (PlatformInfo platform in platforms) { pathsToRebuild.AddRange(GetPathsToRebuild(allPlatforms, platform, repoData)); } } return(pathsToRebuild.Distinct().ToList()); }
private async Task <IEnumerable <string> > GetPathsToRebuildAsync(Subscription subscription) { // If the command is filtered with an OS type that does not match the OsType filter of the subscription, // then there are no images that need to be inspected. string osTypeRegexPattern = ManifestFilter.GetFilterRegexPattern(Options.FilterOptions.OsType); if (!String.IsNullOrEmpty(subscription.OsType) && !Regex.IsMatch(subscription.OsType, osTypeRegexPattern, RegexOptions.IgnoreCase)) { return(Enumerable.Empty <string>()); } RepoData[] repos = await GetImageInfoForSubscriptionAsync(subscription); string repoPath = await GetGitRepoPath(subscription); TempManifestOptions manifestOptions = new TempManifestOptions(Options.FilterOptions) { Manifest = Path.Combine(repoPath, subscription.ManifestPath) }; ManifestInfo manifest = ManifestInfo.Load(manifestOptions); List <string> pathsToRebuild = new List <string>(); IEnumerable <PlatformInfo> allPlatforms = manifest.GetAllPlatforms().ToList(); foreach (RepoInfo repo in manifest.FilteredRepos) { IEnumerable <PlatformInfo> platforms = repo.FilteredImages .SelectMany(image => image.FilteredPlatforms); RepoData repoData = repos .FirstOrDefault(s => s.Repo == repo.Model.Name); foreach (var platform in platforms) { if (repoData != null && repoData.Images != null && repoData.Images.TryGetValue(platform.DockerfilePathRelativeToManifest, out ImageData imageData)) { bool hasDigestChanged = false; foreach (string fromImage in platform.ExternalFromImages) { string currentDigest; await this.imageDigestsSemaphore.WaitAsync(); try { if (!this.imageDigests.TryGetValue(fromImage, out currentDigest)) { this.dockerService.PullImage(fromImage, Options.IsDryRun); currentDigest = this.dockerService.GetImageDigest(fromImage, Options.IsDryRun); this.imageDigests.Add(fromImage, currentDigest); } } finally { this.imageDigestsSemaphore.Release(); } string lastDigest = null; imageData.BaseImages?.TryGetValue(fromImage, out lastDigest); if (lastDigest != currentDigest) { hasDigestChanged = true; break; } } if (hasDigestChanged) { IEnumerable <PlatformInfo> dependentPlatforms = platform.GetDependencyGraph(allPlatforms); pathsToRebuild.AddRange(dependentPlatforms.Select(p => p.Model.Dockerfile)); } } else { this.loggerService.WriteMessage( $"WARNING: Image info not found for '{platform.DockerfilePath}'. Adding path to build to be queued anyway."); pathsToRebuild.Add(platform.Model.Dockerfile); } } } return(pathsToRebuild.Distinct().ToList()); }