private async Task ProcessRepoAsync(
            IAcrClient acrClient, Task <Repository> getRepoTask, List <string> deletedRepos, List <string> deletedImages)
        {
            Repository repository = await getRepoTask;

            switch (Options.Action)
            {
            case CleanAcrImagesAction.PruneDangling:
                await ProcessManifestsAsync(acrClient, deletedImages, deletedRepos, repository,
                                            manifest => !manifest.Tags.Any() && IsExpired(manifest.LastUpdateTime, Options.Age));

                break;

            case CleanAcrImagesAction.PruneAll:
                await ProcessManifestsAsync(acrClient, deletedImages, deletedRepos, repository,
                                            manifest => IsExpired(manifest.LastUpdateTime, Options.Age));

                break;

            case CleanAcrImagesAction.Delete:
                if (IsExpired(repository.LastUpdateTime, Options.Age))
                {
                    await DeleteRepositoryAsync(acrClient, deletedRepos, repository);
                }
                break;

            default:
                throw new NotSupportedException($"Unsupported action: {Options.Action}");
            }
        }
        private async Task LogSummaryAsync(IAcrClient acrClient, List <string> deletedRepos, List <string> deletedImages)
        {
            _loggerService.WriteHeading("SUMMARY");

            _loggerService.WriteSubheading("Deleted repositories:");
            foreach (string deletedRepo in deletedRepos)
            {
                _loggerService.WriteMessage($"\t{deletedRepo}");
            }

            _loggerService.WriteMessage();

            _loggerService.WriteSubheading("Deleted images:");
            foreach (string deletedImage in deletedImages)
            {
                _loggerService.WriteMessage($"\t{deletedImage}");
            }

            _loggerService.WriteMessage();

            _loggerService.WriteSubheading("DELETED DATA");
            _loggerService.WriteMessage($"Total images deleted: {deletedImages.Count}");
            _loggerService.WriteMessage($"Total repos deleted: {deletedRepos.Count}");
            _loggerService.WriteMessage();

            _loggerService.WriteMessage("<Querying remaining data...>");

            // Requery the catalog to get the latest info after things have been deleted
            Catalog catalog = await acrClient.GetCatalogAsync();

            _loggerService.WriteSubheading($"Total repos remaining: {catalog.RepositoryNames.Count}");
        }
        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 async Task ProcessManifestsAsync(
            IAcrClient acrClient, List <string> deletedImages, List <string> deletedRepos, Repository repository,
            Func <ManifestAttributes, bool> canDeleteManifest)
        {
            _loggerService.WriteMessage($"Querying manifests for repo '{repository.Name}'");
            RepositoryManifests repoManifests = await acrClient.GetRepositoryManifestsAsync(repository.Name);

            _loggerService.WriteMessage($"Finished querying manifests for repo '{repository.Name}'. Manifest count: {repoManifests.Manifests.Count}");

            if (!repoManifests.Manifests.Any())
            {
                await DeleteRepositoryAsync(acrClient, deletedRepos, repository);

                return;
            }

            ManifestAttributes[] expiredTestImages = repoManifests.Manifests
                                                     .Where(manifest => canDeleteManifest(manifest))
                                                     .ToArray();

            // If all the images in the repo are expired, delete the whole repo instead of
            // deleting each individual image.
            if (expiredTestImages.Length == repoManifests.Manifests.Count)
            {
                await DeleteRepositoryAsync(acrClient, deletedRepos, repository);

                return;
            }

            await DeleteManifestsAsync(acrClient, deletedImages, repository, expiredTestImages);
        }
        private async Task DeleteManifestsAsync(
            IAcrClient acrClient, List <string> deletedImages, Repository repository, IEnumerable <ManifestAttributes> manifests)
        {
            List <Task> tasks = new List <Task>();

            foreach (ManifestAttributes manifest in manifests)
            {
                tasks.Add(DeleteManifestAsync(acrClient, deletedImages, repository, manifest));
            }

            await Task.WhenAll(tasks);
        }
        private async Task DeleteRepositoryAsync(IAcrClient acrClient, List <string> deletedRepos, Repository repository)
        {
            string[] manifestsDeleted;
            string[] tagsDeleted;

            ManifestAttributes[] manifests = (await acrClient.GetRepositoryManifestsAsync(repository.Name)).Manifests.ToArray();

            if (!Options.IsDryRun)
            {
                DeleteRepositoryResponse deleteResponse =
                    await acrClient.DeleteRepositoryAsync(repository.Name);

                manifestsDeleted = deleteResponse.ManifestsDeleted;
                tagsDeleted      = deleteResponse.TagsDeleted;
            }
            else
            {
                manifestsDeleted = manifests
                                   .Select(manifest => manifest.Digest)
                                   .ToArray();

                tagsDeleted = manifests
                              .SelectMany(manifest => manifest.Tags)
                              .ToArray();
            }

            StringBuilder messageBuilder = new StringBuilder();

            messageBuilder.AppendLine($"Deleted repository '{repository.Name}'");
            messageBuilder.AppendLine($"\tIncluded manifests:");
            foreach (string manifest in manifestsDeleted.OrderBy(manifest => manifest))
            {
                messageBuilder.AppendLine($"\t{manifest}");
            }

            messageBuilder.AppendLine();
            messageBuilder.AppendLine($"\tIncluded tags:");
            foreach (string tag in tagsDeleted.OrderBy(tag => tag))
            {
                messageBuilder.AppendLine($"\t{tag}");
            }

            _loggerService.WriteMessage(messageBuilder.ToString());

            lock (deletedRepos)
            {
                deletedRepos.Add(repository.Name);
            }
        }
        private async Task DeleteManifestAsync(
            IAcrClient acrClient, List <string> deletedImages, Repository repository, ManifestAttributes manifest)
        {
            if (!Options.IsDryRun)
            {
                await acrClient.DeleteManifestAsync(repository.Name, manifest.Digest);
            }

            string imageId = $"{repository.Name}@{manifest.Digest}";

            _loggerService.WriteMessage($"Deleted image '{imageId}'");

            lock (deletedImages)
            {
                deletedImages.Add(imageId);
            }
        }