public async Task <int> InvokeAsync(IEnumerable <string> arguments) { Options.Parse(arguments); if (_showHelp) { Options.WriteOptionDescriptions(CommandSet.Out); return(0); } if (string.IsNullOrEmpty(_rolloutScorer.OutputFile)) { _rolloutScorer.OutputFile = Path.Combine(Directory.GetCurrentDirectory(), $"{_rolloutScorer.Repo}-{_rolloutScorer.RolloutStartDate.Date.ToShortDateString().Replace("/","-")}-scorecard.csv"); } _rolloutScorer.RolloutWeightConfig = StandardConfig.DefaultConfig.RolloutWeightConfig; _rolloutScorer.GithubConfig = StandardConfig.DefaultConfig.GithubConfig; // If they haven't told us to upload but they also haven't specified a repo & rollout start date, we need to throw if (string.IsNullOrEmpty(_rolloutScorer.Repo) || (_rolloutScorer.RolloutStartDate == null)) { Utilities.WriteError($"ERROR: One or both of required parameters 'repo' and 'rollout-start-date' were not specified."); return(1); } _rolloutScorer.RepoConfig = StandardConfig.DefaultConfig.RepoConfigs.Find(r => r.Repo == _rolloutScorer.Repo); if (_rolloutScorer.RepoConfig == null) { Utilities.WriteError($"ERROR: Provided repo '{_rolloutScorer.Repo}' does not exist in config file"); return(1); } _rolloutScorer.AzdoConfig = StandardConfig.DefaultConfig.AzdoInstanceConfigs.Find(a => a.Name == _rolloutScorer.RepoConfig.AzdoInstance); if (_rolloutScorer.AzdoConfig == null) { Utilities.WriteError($"ERROR: Configuration file is invalid; repo '{_rolloutScorer.RepoConfig.Repo}' " + $"references unknown AzDO instance '{_rolloutScorer.RepoConfig.AzdoInstance}'"); return(1); } // Get the AzDO & GitHub PATs from key vault AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider(); SecretBundle githubPat; SecretBundle storageAccountConnectionString; using (KeyVaultClient kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback))) { Console.WriteLine("Fetching PATs from key vault."); _rolloutScorer.SetupHttpClient((await kv.GetSecretAsync(_rolloutScorer.AzdoConfig.KeyVaultUri, _rolloutScorer.AzdoConfig.PatSecretName)).Value); githubPat = await kv.GetSecretAsync(Utilities.KeyVaultUri, Utilities.GitHubPatSecretName); _rolloutScorer.SetupGithubClient(githubPat.Value); storageAccountConnectionString = await kv.GetSecretAsync(Utilities.KeyVaultUri, ScorecardsStorageAccount.KeySecretName); } try { await _rolloutScorer.InitAsync(); } catch (ArgumentException e) { Utilities.WriteError(e.Message); return(1); } Scorecard scorecard = await Scorecard.CreateScorecardAsync(_rolloutScorer); string expectedTimeToRollout = TimeSpan.FromMinutes(_rolloutScorer.RepoConfig.ExpectedTime).ToString(); Console.WriteLine($"The {_rolloutScorer.Repo} {_rolloutScorer.RolloutStartDate.Date.ToShortDateString()} rollout score is {scorecard.TotalScore}.\n"); Console.WriteLine($"| Metric | Value | Target | Score |"); Console.WriteLine($"|:--------------------------------:|:--------:|:--------:|:---------:|"); Console.WriteLine($"| Time to Rollout | {scorecard.TimeToRollout} | {expectedTimeToRollout} | {scorecard.TimeToRolloutScore} |"); Console.WriteLine($"| Critical/blocking issues created | {scorecard.CriticalIssues} | 0 | {scorecard.CriticalIssueScore} |"); Console.WriteLine($"| Hotfixes | {scorecard.Hotfixes} | 0 | {scorecard.HotfixScore} |"); Console.WriteLine($"| Rollbacks | {scorecard.Rollbacks} | 0 | {scorecard.RollbackScore} |"); Console.WriteLine($"| Service downtime | {scorecard.Downtime} | 00:00:00 | {scorecard.DowntimeScore} |"); Console.WriteLine($"| Failed to rollout | {scorecard.Failure.ToString().ToUpperInvariant()} | FALSE | {(scorecard.Failure ? StandardConfig.DefaultConfig.RolloutWeightConfig.FailurePoints : 0)} |"); Console.WriteLine($"| Total | | | **{scorecard.TotalScore}** |"); if (_rolloutScorer.Upload) { Console.WriteLine("Directly uploading results."); await RolloutUploader.UploadResultsAsync(new List <Scorecard> { scorecard }, Utilities.GetGithubClient(githubPat.Value), storageAccountConnectionString.Value, _rolloutScorer.GithubConfig); } if (_rolloutScorer.SkipOutput) { Console.WriteLine("Skipping output step."); } else { if (await scorecard.Output(_rolloutScorer.OutputFile) != 0) { return(1); } Console.WriteLine($"Wrote output to file {_rolloutScorer.OutputFile}"); } return(0); }
public static async Task Run([TimerTrigger("0 0 0 * * *")] TimerInfo myTimer, ILogger log) { AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider(); string deploymentEnvironment = Environment.GetEnvironmentVariable("DeploymentEnvironment") ?? "Staging"; log.LogInformation($"INFO: Deployment Environment: {deploymentEnvironment}"); log.LogInformation("INFO: Getting storage account keys from KeyVault..."); SecretBundle scorecardsStorageAccountKey = await GetStorageAccountKeyAsync(tokenProvider, Utilities.KeyVaultUri, ScorecardsStorageAccount.KeySecretName); SecretBundle deploymentTableSasToken = await GetStorageAccountKeyAsync(tokenProvider, "https://DotNetEng-Status-Prod.vault.azure.net", "deployment-table-sas-token"); log.LogInformation("INFO: Getting cloud tables..."); CloudTable scorecardsTable = Utilities.GetScorecardsCloudTable(scorecardsStorageAccountKey.Value); CloudTable deploymentsTable = new CloudTable( new Uri($"https://dotnetengstatusprod.table.core.windows.net/deployments{deploymentTableSasToken.Value}")); List <ScorecardEntity> scorecardEntries = await GetAllTableEntriesAsync <ScorecardEntity>(scorecardsTable); scorecardEntries.Sort((x, y) => x.Date.CompareTo(y.Date)); List <AnnotationEntity> deploymentEntries = await GetAllTableEntriesAsync <AnnotationEntity>(deploymentsTable); deploymentEntries.Sort((x, y) => (x.Ended ?? DateTimeOffset.MaxValue).CompareTo(y.Ended ?? DateTimeOffset.MaxValue)); log.LogInformation($"INFO: Found {scorecardEntries?.Count ?? -1} scorecard table entries and {deploymentEntries?.Count ?? -1} deployment table entries." + $"(-1 indicates that null was returned.)"); // The deployments we care about are ones that occurred after the last scorecard IEnumerable <AnnotationEntity> relevantDeployments = deploymentEntries.Where(d => (d.Ended ?? DateTimeOffset.MaxValue) > scorecardEntries.Last().Date.AddDays(ScoringBufferInDays)); log.LogInformation($"INFO: Found {relevantDeployments?.Count() ?? -1} relevant deployments (deployments which occurred " + $"after the last scorecard). (-1 indicates that null was returned.)"); if (relevantDeployments.Count() > 0) { log.LogInformation("INFO: Checking to see if the most recent deployment occurred more than two days ago..."); // We have only want to score if the buffer period has elapsed since the last deployment if ((relevantDeployments.Last().Ended ?? DateTimeOffset.MaxValue) < DateTimeOffset.UtcNow - TimeSpan.FromDays(ScoringBufferInDays)) { var scorecards = new List <Scorecard>(); log.LogInformation("INFO: Rollouts will be scored. Fetching GitHub PAT..."); SecretBundle githubPat; using (KeyVaultClient kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback))) { githubPat = await kv.GetSecretAsync(Utilities.KeyVaultUri, Utilities.GitHubPatSecretName); } // We'll score the deployments by service foreach (var deploymentGroup in relevantDeployments.GroupBy(d => d.Service)) { log.LogInformation($"INFO: Scoring {deploymentGroup?.Count() ?? -1} rollouts for repo '{deploymentGroup.Key}'"); RolloutScorer.RolloutScorer rolloutScorer = new RolloutScorer.RolloutScorer { Repo = deploymentGroup.Key, RolloutStartDate = deploymentGroup.First().Started.GetValueOrDefault().Date, RolloutWeightConfig = Configs.DefaultConfig.RolloutWeightConfig, GithubConfig = Configs.DefaultConfig.GithubConfig, Log = log, }; log.LogInformation($"INFO: Finding repo config for {rolloutScorer.Repo}..."); rolloutScorer.RepoConfig = Configs.DefaultConfig.RepoConfigs .Find(r => r.Repo == rolloutScorer.Repo); log.LogInformation($"INFO: Repo config: {rolloutScorer.RepoConfig.Repo}"); log.LogInformation($"INFO: Finding AzDO config for {rolloutScorer.RepoConfig.AzdoInstance}..."); rolloutScorer.AzdoConfig = Configs.DefaultConfig.AzdoInstanceConfigs .Find(a => a.Name == rolloutScorer.RepoConfig.AzdoInstance); log.LogInformation($"INFO: Fetching AzDO PAT from KeyVault..."); using (KeyVaultClient kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback))) { rolloutScorer.SetupHttpClient( (await kv.GetSecretAsync(rolloutScorer.AzdoConfig.KeyVaultUri, rolloutScorer.AzdoConfig.PatSecretName)).Value); } rolloutScorer.SetupGithubClient(githubPat.Value); log.LogInformation($"INFO: Attempting to initialize RolloutScorer..."); try { await rolloutScorer.InitAsync(); } catch (ArgumentException e) { log.LogError($"ERROR: Error while processing {rolloutScorer.RolloutStartDate} rollout of {rolloutScorer.Repo}."); log.LogError($"ERROR: {e.Message}"); continue; } log.LogInformation($"INFO: Creating rollout scorecard..."); scorecards.Add(await Scorecard.CreateScorecardAsync(rolloutScorer)); log.LogInformation($"INFO: Successfully created scorecard for {rolloutScorer.RolloutStartDate.Date} rollout of {rolloutScorer.Repo}."); } log.LogInformation($"INFO: Uploading results for {string.Join(", ", scorecards.Select(s => s.Repo))}"); await RolloutUploader.UploadResultsAsync(scorecards, Utilities.GetGithubClient(githubPat.Value), scorecardsStorageAccountKey.Value, Configs.DefaultConfig.GithubConfig, skipPr : deploymentEnvironment != "Production"); } else { log.LogInformation(relevantDeployments.Last().Ended.HasValue ? $"INFO: Most recent rollout occurred less than two days ago " + $"({relevantDeployments.Last().Service} on {relevantDeployments.Last().Ended.Value}); waiting to score." : $"Most recent rollout ({relevantDeployments.Last().Service}) is still in progress."); } } else { log.LogInformation($"INFO: Found no rollouts which occurred after last recorded rollout " + $"({(scorecardEntries.Count > 0 ? $"date {scorecardEntries.Last().Date}" : "no rollouts in table")})"); } }
public static async Task Run([TimerTrigger("0 0 0 * * *")] TimerInfo myTimer, ILogger log) { AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider(); SecretBundle scorecardsStorageAccountKey = await GetStorageAccountKeyAsync(tokenProvider, Utilities.KeyVaultUri, ScorecardsStorageAccount.KeySecretName); SecretBundle deploymentTableSasToken = await GetStorageAccountKeyAsync(tokenProvider, "https://DotNetEng-Status-Prod.vault.azure.net", "deployment-table-sas-token"); CloudTable scorecardsTable = Utilities.GetScorecardsCloudTable(scorecardsStorageAccountKey.Value); CloudTable deploymentsTable = new CloudTable( new Uri($"https://dotnetengstatusprod.table.core.windows.net/deployments{deploymentTableSasToken.Value}")); List <ScorecardEntity> scorecardEntries = await GetAllTableEntriesAsync <ScorecardEntity>(scorecardsTable); scorecardEntries.Sort((x, y) => x.Date.CompareTo(y.Date)); List <AnnotationEntity> deploymentEntries = await GetAllTableEntriesAsync <AnnotationEntity>(deploymentsTable); deploymentEntries.Sort((x, y) => (x.Ended ?? DateTimeOffset.MaxValue).CompareTo(y.Ended ?? DateTimeOffset.MaxValue)); // The deployments we care about are ones that occurred after the last scorecard IEnumerable <AnnotationEntity> relevantDeployments = deploymentEntries.Where(d => (d.Ended ?? DateTimeOffset.MaxValue) > scorecardEntries.Last().Date); if (relevantDeployments.Count() > 0) { // We have only want to score if the buffer period has elapsed since the last deployment if ((relevantDeployments.Last().Ended ?? DateTimeOffset.MaxValue) < DateTimeOffset.UtcNow - TimeSpan.FromDays(ScoringBufferInDays)) { var scorecards = new List <Scorecard>(); SecretBundle githubPat; using (KeyVaultClient kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback))) { githubPat = await kv.GetSecretAsync(Utilities.KeyVaultUri, Utilities.GitHubPatSecretName); } // We'll score the deployments by service foreach (var deploymentGroup in relevantDeployments.GroupBy(d => d.Service)) { RolloutScorer.RolloutScorer rolloutScorer = new RolloutScorer.RolloutScorer { Repo = deploymentGroup.Key, RolloutStartDate = deploymentGroup.First().Started.GetValueOrDefault().Date, RolloutWeightConfig = Configs.DefaultConfig.RolloutWeightConfig, GithubConfig = Configs.DefaultConfig.GithubConfig, Log = log, }; rolloutScorer.RepoConfig = Configs.DefaultConfig.RepoConfigs .Find(r => r.Repo == rolloutScorer.Repo); rolloutScorer.AzdoConfig = Configs.DefaultConfig.AzdoInstanceConfigs .Find(a => a.Name == rolloutScorer.RepoConfig.AzdoInstance); using (KeyVaultClient kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback))) { rolloutScorer.SetupHttpClient( (await kv.GetSecretAsync(rolloutScorer.AzdoConfig.KeyVaultUri, rolloutScorer.AzdoConfig.PatSecretName)).Value); } rolloutScorer.SetupGithubClient(githubPat.Value); try { await rolloutScorer.InitAsync(); } catch (ArgumentException e) { log.LogError($"Error while processing {rolloutScorer.RolloutStartDate} rollout of {rolloutScorer.Repo}."); log.LogError(e.Message); continue; } scorecards.Add(await Scorecard.CreateScorecardAsync(rolloutScorer)); log.LogInformation($"Successfully created scorecard for {rolloutScorer.RolloutStartDate.Date} rollout of {rolloutScorer.Repo}."); } log.LogInformation($"Uploading results for {string.Join(", ", scorecards.Select(s => s.Repo))}"); await RolloutUploader.UploadResultsAsync(scorecards, Utilities.GetGithubClient(githubPat.Value), scorecardsStorageAccountKey.Value, Configs.DefaultConfig.GithubConfig); } else { log.LogInformation(relevantDeployments.Last().Ended.HasValue ? $"Most recent rollout occurred less than two days ago " + $"({relevantDeployments.Last().Service} on {relevantDeployments.Last().Ended.Value}); waiting to score." : $"Most recent rollout ({relevantDeployments.Last().Service}) is still in progress."); } } else { log.LogInformation($"Found no rollouts which occurred after last recorded rollout (date {scorecardEntries.Last().Date})"); } }