Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
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})");
            }
        }