diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index f6abcedb6f..831f590032 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "microsoft.dnceng.secretmanager": { - "version": "1.1.0-beta.24351.3", + "version": "1.1.0-beta.24374.1", "commands": [ "secret-manager" ] @@ -15,7 +15,7 @@ ] }, "microsoft.dnceng.configuration.bootstrap": { - "version": "1.1.0-beta.24351.3", + "version": "1.1.0-beta.24374.1", "commands": [ "bootstrap-dnceng-configuration" ] diff --git a/.vault-config/helixkv.yaml b/.vault-config/helixkv.yaml index 2e201900f0..817e9e0df0 100644 --- a/.vault-config/helixkv.yaml +++ b/.vault-config/helixkv.yaml @@ -4,27 +4,9 @@ storageLocation: subscription: a4fc5514-21a9-4296-bfaf-5c7ee7fa35d1 name: helixkv - secrets: dn-bot-account-redmond: type: domain-account parameters: accountName: dn-bot description: The dn-bot account - - dnb2-bot-account-redmond: - type: domain-account - parameters: - accountName: dnb2-bot - description: The dnb2-bot account - - dn-dependabot-account-redmond: - type: domain-account - parameters: - accountName: dn-dependabot - description: The dn-dependabot account - - dotnet-mc-bot-account: - type: github-account - parameters: - name: dotnet-mc-bot diff --git a/.vault-config/maestrolocal.yaml b/.vault-config/maestrolocal.yaml index 33cb25d878..dae4a9b0ef 100644 --- a/.vault-config/maestrolocal.yaml +++ b/.vault-config/maestrolocal.yaml @@ -19,11 +19,6 @@ secrets: hasWebhookSecret: false hasOAuthSecret: true - prod-maestro-token: - type: maestro-access-token - parameters: - environment: https://maestro.dot.net/ - dn-bot-dnceng-build-rw-code-rw-release-rw: type: azure-devops-access-token parameters: @@ -33,53 +28,3 @@ secrets: name: dn-bot-account-redmond organizations: dnceng scopes: build_execute code_write release_execute - - dn-bot-devdiv-build-rw-code-rw-release-rw: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: devdiv - scopes: build_execute code_write release_execute - - dn-bot-domoreexp-build-rw-code-rw-release-rw: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: domoreexp - scopes: build_execute code_write release_execute - - dn-bot-dnceng-packaging-rwm: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: dnceng - scopes: packaging_manage - - dn-bot-dnceng-build-r: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: dnceng - scopes: build - - dn-bot-dnceng-public-build-r: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: dnceng-public - scopes: build \ No newline at end of file diff --git a/.vault-config/product-construction-dev.yaml b/.vault-config/product-construction-dev.yaml index 278b23f4b2..d73c9d807e 100644 --- a/.vault-config/product-construction-dev.yaml +++ b/.vault-config/product-construction-dev.yaml @@ -25,14 +25,3 @@ secrets: location: engkeyvault name: BotAccount-dotnet-bot gitHubBotAccountName: dotnet-bot - - dn-bot-all-orgs-code-r: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - name: dn-bot-all-orgs-code-r - organizations: devdiv dnceng - scopes: code diff --git a/.vault-config/product-construction-int.yaml b/.vault-config/product-construction-int.yaml index e7c3cb9eb3..7d597b7356 100644 --- a/.vault-config/product-construction-int.yaml +++ b/.vault-config/product-construction-int.yaml @@ -26,17 +26,6 @@ secrets: name: BotAccount-dotnet-bot gitHubBotAccountName: dotnet-bot - dn-bot-all-orgs-code-r: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - name: dn-bot-all-orgs-code-r - organizations: devdiv dnceng - scopes: code - github: type: github-app-secret parameters: diff --git a/.vault-config/shared/maestro-secrets.yaml b/.vault-config/shared/maestro-secrets.yaml index f7cc052477..544b4c9f99 100644 --- a/.vault-config/shared/maestro-secrets.yaml +++ b/.vault-config/shared/maestro-secrets.yaml @@ -5,11 +5,6 @@ github: hasWebhookSecret: true hasOAuthSecret: true -prod-maestro-token: - type: maestro-access-token - parameters: - environment: https://maestro.dot.net/ - dn-bot-dnceng-build-rw-code-rw-release-rw: type: azure-devops-access-token parameters: @@ -19,53 +14,3 @@ dn-bot-dnceng-build-rw-code-rw-release-rw: name: dn-bot-account-redmond organizations: dnceng scopes: build_execute code_write release_execute - -dn-bot-devdiv-build-rw-code-rw-release-rw: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: devdiv - scopes: build_execute code_write release_execute - -dn-bot-domoreexp-build-rw-code-rw-release-rw: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: domoreexp - scopes: build_execute code_write release_execute - -dn-bot-dnceng-packaging-rwm: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: dnceng - scopes: packaging_manage - -dn-bot-dnceng-build-r: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: dnceng - scopes: build - -dn-bot-dnceng-public-build-r: - type: azure-devops-access-token - parameters: - domainAccountName: dn-bot - domainAccountSecret: - location: helixkv - name: dn-bot-account-redmond - organizations: dnceng-public - scopes: build diff --git a/Directory.Packages.props b/Directory.Packages.props index b65f247a57..47f50839ad 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ 3.1.24 6.0.26 6.0.0 - 8.0.2 + 8.1.0 true true @@ -17,7 +17,7 @@ - + @@ -39,7 +39,6 @@ - @@ -121,11 +120,11 @@ - - - + + + - + diff --git a/azure-pipelines-product-construction-service.yml b/azure-pipelines-product-construction-service.yml index c6fe4fb83e..3d6fcf4887 100644 --- a/azure-pipelines-product-construction-service.yml +++ b/azure-pipelines-product-construction-service.yml @@ -20,7 +20,6 @@ variables: value: $(Build.ArtifactStagingDirectory)/diff - ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: # https://dev.azure.com/dnceng/internal/_library?itemType=VariableGroups&view=VariableGroupView&variableGroupId=189 - - group: Product-Construction-Service-Int - name: containerappName value: product-construction-int - name: containerRegistryName @@ -69,9 +68,14 @@ stages: displayName: Build docker image - ${{ if notin(variables['Build.Reason'], 'PullRequest') }}: - - powershell: | - echo $(container-registry-password) | docker login --username $(container-registry-username) --password-stdin $(dockerRegistryUrl) - docker push "$(dockerRegistryUrl)/$(containerName):$(newDockerImageTag)" + - task: AzureCLI@2 + inputs: + azureSubscription: $(serviceConnectionName) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + az acr login --name $(containerRegistryName) + docker push "$(dockerRegistryUrl)/$(containerName):$(newDockerImageTag)" displayName: Push docker image - ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/production') }}: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e501a345d8..33d086a80e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -160,27 +160,6 @@ extends: artifact: Maestro.ScenarioTests displayName: Publish Maestro Scenario Tests condition: always() - - - ${{ if in(variables['Build.SourceBranch'], 'refs/heads/main', 'refs/heads/production') }}: - - template: /eng/common/templates-official/post-build/post-build.yml@self - parameters: - enableSymbolValidation: true - enableSigningValidation: false - artifactsPublishingAdditionalParameters: '/p:CheckEolTargetFramework=false' - symbolPublishingAdditionalParameters: '/p:CheckEolTargetFramework=false' - SDLValidationParameters: - enable: true - params: '-SourceToolsList @("policheck","credscan") - -TsaInstanceURL $(_TsaInstanceURL) - -TsaProjectName $(_TsaProjectName) - -TsaNotificationEmail $(_TsaNotificationEmail) - -TsaCodebaseAdmin $(_TsaCodebaseAdmin) - -TsaBugAreaPath $(_TsaBugAreaPath) - -TsaIterationPath $(_TsaIterationPath) - -TsaRepositoryName "Arcade-Services" - -TsaCodebaseName "Arcade-Services" - -TsaPublish $True - -PoliCheckAdditionalRunConfigParams @("UserExclusionPath < $(Build.SourcesDirectory)/eng/PoliCheckExclusions.xml")' - template: /eng/templates/stages/deploy.yaml@self parameters: @@ -202,3 +181,24 @@ extends: VariableGroup: MaestroProd KeyVault BarConnectionString: "Data Source=tcp:maestro-prod.database.windows.net,1433; Initial Catalog=BuildAssetRegistry; Authentication=Active Directory Default; Persist Security Info=False; MultipleActiveResultSets=True; Connect Timeout=120; Encrypt=True; TrustServerCertificate=False; User Id=1093df3b-c754-4788-a4ae-ea33b86b82aa" BarMigrationSubscription: BarMigrationProd + + - ${{ if in(variables['Build.SourceBranch'], 'refs/heads/main', 'refs/heads/production') }}: + - template: /eng/common/templates-official/post-build/post-build.yml@self + parameters: + enableSymbolValidation: true + enableSigningValidation: false + artifactsPublishingAdditionalParameters: '/p:CheckEolTargetFramework=false' + symbolPublishingAdditionalParameters: '/p:CheckEolTargetFramework=false' + SDLValidationParameters: + enable: true + params: '-SourceToolsList @("policheck","credscan") + -TsaInstanceURL $(_TsaInstanceURL) + -TsaProjectName $(_TsaProjectName) + -TsaNotificationEmail $(_TsaNotificationEmail) + -TsaCodebaseAdmin $(_TsaCodebaseAdmin) + -TsaBugAreaPath $(_TsaBugAreaPath) + -TsaIterationPath $(_TsaIterationPath) + -TsaRepositoryName "Arcade-Services" + -TsaCodebaseName "Arcade-Services" + -TsaPublish $True + -PoliCheckAdditionalRunConfigParams @("UserExclusionPath < $(Build.SourcesDirectory)/eng/PoliCheckExclusions.xml")' diff --git a/docs/DevGuide.md b/docs/DevGuide.md index 19e726dd73..9aa4912197 100644 --- a/docs/DevGuide.md +++ b/docs/DevGuide.md @@ -1,14 +1,11 @@ # Gettings started developing ## Getting started -1. Install Visual Studio 2019 with the '.NET Core', 'Desktop Development with C++'. and 'Azure Development' workloads. +1. Install Visual Studio with 'Desktop Development with C++'. and 'Azure Development' workloads. 1. Install Azure Service Fabric SDK: https://www.microsoft.com/web/handlers/webpi.ashx?command=getinstallerredirect&appid=MicrosoftAzure-ServiceFabric-CoreSDK 1. Install SQL Server Express: https://www.microsoft.com/en-us/sql-server/sql-server-downloads 1. Install Node.js LTS. When asked, at the end of installation, also opt-in for all necessary tools. -1. Acquire the required secrets from Azure key vault. This can be done by running [/src/Maestro/bootstrap.ps1](../src/Maestro/bootstrap.ps1) from an admin powershell window (note: the Powershell ISE may have problems running this script). This script will do three things: - - Download a secret required for using the `Microsoft.Azure.Services.AppAuthentication` package from the service fabric local dev cluster - - Download and install the SSL cert used for local development from key vault - - Configure the SQL Server LocalDB instance for use from the local Service Fabric cluster +1. Acquire the required secrets from Azure key vault. This can be done by running [/src/Maestro/bootstrap.ps1](../src/Maestro/bootstrap.ps1) from an admin powershell window (note: the Powershell ISE may have problems running this script). This script will download a secret required for using the `Microsoft.Azure.Services.AppAuthentication` package from the service fabric local dev cluster. 1. Make sure you have installed Entity Framework Core CLI by running `dotnet tool install --global dotnet-ef` 1. From the `src\Maestro\Maestro.Data` project directory, run `dotnet ef --msbuildprojectextensionspath database update`. - Note that the generated files are in the root artifacts folder, not the artifacts folder within the Maestro.Data project folder @@ -24,16 +21,21 @@ ``` 1. Run `.\Build.cmd -pack` at the root of the repo -1. Install ngrok from https://ngrok.com/ or `choco install ngrok` -1. (optional - when darc is used) Run `ngrok http 8080` and then use the reported ngrok url for the --bar-uri darc argument +1. Get access to the [maestrolocal KeyVault](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/cab65fc3-d077-467d-931f-3932eabf36d3/resourceGroups/maestro/providers/Microsoft.KeyVault/vaults/maestrolocal/overview) +1. Get assigned to users of the [staging Maestro Entra application](https://ms.portal.azure.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/Users/objectId/8b6d0440-8a2f-438e-84b7-d6ffaa401ca3/appId/baf98f1b-374e-487d-af42-aa33807f11e4) After successfully running `bootstrap.ps1` running the `MaestroApplication` project via F5 in VS (launch as elevated) will run the application on `http://localhost:8080`. It seems that calling `bootstrap.ps1` is not a one-time operation and needs to be called every time you restart your machine. -### Local developer workflow +## Local developer workflow -The guaranteed way (some steps might be extraneous but assured to work) to successfully (re-)deploy Maestro locally after you're iterating on the code is to: +There are two main ways how you can run the Maestro application locally. + +The first one is to run the BarViz web application + Maestro API only. This can be done by F5-ing the `Maestro.Web` project directly from Visual Studio. This is suitable for testing the REST API and the front-end part of the web application (angular). + +The second way is to run the full Maestro in a local Service Fabric cluster. This takes a bit more time to start but will also run backend services like the dependency flow (pull request actos..). +The guaranteed way (some steps might be extraneous but assured to work) to successfully (re-)deploy the Service Fabric cluster locally after you're iterating on the code is to: - Make sure you have run `bootstrap.ps1` after the last reboot - Reset the local SF cluster (`Service Fabric Local Cluster Manager` -> `Reset Local Cluster`) - Start the VS in Administrator mode @@ -43,7 +45,7 @@ In case you need to also run PCS locally, make sure you've read the [PCS Readme] - either run `dotnet run` in `src/ProductConstructionService/ProductConstructionService.AppHost`, - or open another instance of VS and F5 the `ProductConstructionService.AppHost` project. -#### How to tell it's done +### How to tell it's done - The Build log (in VS) shows ``` 3>Finished executing script 'Deploy-FabricApplication.ps1' @@ -67,13 +69,19 @@ Maestro.Web uses Azure AppConfiguration (AAC) to dynamically enable/disable auto - https://zimmergren.net/introduction-azure-app-configuration-store-csharp-dotnetcore/ - https://docs.microsoft.com/en-us/azure/azure-app-configuration/howto-integrate-azure-managed-service-identity +## Deploying a branch to Staging + +You can deploy your branch to the staging environment where E2E tests can be run using the following steps: +- Notify others in the team that you are deploying to staging. The Staging environment is shared so please check if another `main` build is not running or others are not deploying to staging or about to merge a PR. +- Push your branch to the Azure DevOps [dotnet-arcade-services](https://dev.azure.com/dnceng/internal/_git/dotnet-arcade-services) repository. +- Run the [arcade-services-internal-ci](https://dev.azure.com/dnceng/internal/_build?definitionId=252&_a=summary) pipeline from your branch. You can unselect the secret rotation, approval and SDL stages if you want. Those are not required. +- If a conflicting build starts to run, you can cancel one of them and restart them later so that only one pipeline runs the `Deploy` stage at once. + ## Running scenario tests against a local cluster If you want to run the C# scenario tests (make sure that you followed the getting started steps before), you will need to set some environment variables: 1. GITHUB_TOKEN : Get a github PAT from https://github.com/settings/tokens - 1. AZDO_TOKEN Get a Azure DevOps PAT from https://dnceng.visualstudio.com/_usersSettings/tokens (or appropriately matching project name if not dnceng) - 1. MAESTRO_TOKEN : Get a maestro bearer token (you can create one after running maestro app) 1. DARC_PACKAGE_SOURCE : Get the path to the darc nuget package (which would be in `arcade-services\artifacts\packages\Debug\NonShipping\`, see below for getting this built) 1. MAESTRO_BASEURIS : Run ngrok and get the https url @@ -82,12 +90,9 @@ Generally this is easiest if you want to debug both sides to simply have two ins Since Visual Studio's Debug Environment variable settings do not apply to debugging tests, you'll need to set them, preferably from a command prompt: 1. Start command prompt -1. set AZDO_TOKEN=... 1. set GITHUB_TOKEN=... -1. set MAESTRO_TOKEN=... 1. set DARC_PACKAGE_SOURCE=... -1. set MAESTRO_BASEURIS=https://blahblahblah.ngrok.io -1. "C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\Common7\IDE\devenv.com" (adjusted for your VS version / drive) +1. set MAESTRO_BASEURIS=http://localhost:8080 When debugging the tests, you can check this via the Immediate window, e.g. by running `System.Environment.GetEnvironmentVariable("GITHUB_TOKEN")` diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7dc68415ee..1f4fa52865 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,127 +1,127 @@ - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/dnceng-shared - c4ed8b69bcb0a344a9fe2004a5944ca3a24bec1c + 77fb751f40c92262450af10314e965a1a708857b - + https://github.com/dotnet/arcade - fa3d544b066661522f1ec5d5e8cfd461a29b0f8a + 1e2be7464703499cf98e20536fb4da4218c8fce1 - + https://github.com/dotnet/arcade - fa3d544b066661522f1ec5d5e8cfd461a29b0f8a + 1e2be7464703499cf98e20536fb4da4218c8fce1 - + https://github.com/dotnet/arcade - fa3d544b066661522f1ec5d5e8cfd461a29b0f8a + 1e2be7464703499cf98e20536fb4da4218c8fce1 - + https://github.com/dotnet/arcade - fa3d544b066661522f1ec5d5e8cfd461a29b0f8a + 1e2be7464703499cf98e20536fb4da4218c8fce1 - + https://github.com/dotnet/arcade - fa3d544b066661522f1ec5d5e8cfd461a29b0f8a + 1e2be7464703499cf98e20536fb4da4218c8fce1 - + https://github.com/dotnet/arcade - fa3d544b066661522f1ec5d5e8cfd461a29b0f8a + 1e2be7464703499cf98e20536fb4da4218c8fce1 - + https://github.com/dotnet/dnceng - b6850078ffe074f4ad7d4952486d4997b1e029b6 + a1925206b7243e58a95245b07a40d5c336a91de9 - + https://github.com/dotnet/dnceng - b6850078ffe074f4ad7d4952486d4997b1e029b6 + a1925206b7243e58a95245b07a40d5c336a91de9 diff --git a/eng/Versions.props b/eng/Versions.props index f55b21f3e5..37e4f58887 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -9,36 +9,36 @@ true 1.0.0-preview.1 - 8.0.0-beta.24367.1 - 8.0.0-beta.24367.1 - 8.0.0-beta.24367.1 - 8.0.0-beta.24367.1 - 8.0.0-beta.24367.1 + 8.0.0-beta.24376.1 + 8.0.0-beta.24376.1 + 8.0.0-beta.24376.1 + 8.0.0-beta.24376.1 + 8.0.0-beta.24376.1 17.4.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24359.1 - 1.1.0-beta.24351.3 - 1.1.0-beta.24351.3 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24376.1 + 1.1.0-beta.24374.1 + 1.1.0-beta.24374.1 diff --git a/eng/common/templates-official/job/publish-build-assets.yml b/eng/common/templates-official/job/publish-build-assets.yml index ba3e7df815..0117328800 100644 --- a/eng/common/templates-official/job/publish-build-assets.yml +++ b/eng/common/templates-official/job/publish-build-assets.yml @@ -149,7 +149,7 @@ jobs: scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: -BuildId $(BARBuildId) -PublishingInfraVersion 3 - -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/common/templates-official/post-build/post-build.yml b/eng/common/templates-official/post-build/post-build.yml index 0dfa387e7b..b81b8770b3 100644 --- a/eng/common/templates-official/post-build/post-build.yml +++ b/eng/common/templates-official/post-build/post-build.yml @@ -281,7 +281,7 @@ stages: scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: -BuildId $(BARBuildId) -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} - -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index 57a41f0a3e..cc2b346ba8 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -145,7 +145,7 @@ jobs: scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: -BuildId $(BARBuildId) -PublishingInfraVersion 3 - -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index 2db4933468..c3b6a3012f 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -277,7 +277,7 @@ stages: scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: -BuildId $(BARBuildId) -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} - -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/service-templates/ProductConstructionService/provision.bicep b/eng/service-templates/ProductConstructionService/provision.bicep index ec98260aa5..78f51dd954 100644 --- a/eng/service-templates/ProductConstructionService/provision.bicep +++ b/eng/service-templates/ProductConstructionService/provision.bicep @@ -370,7 +370,7 @@ resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-pr name: 'Premium' } properties: { - adminUserEnabled: true + adminUserEnabled: false anonymousPullEnabled: false dataEndpointEnabled: false encryption: { @@ -412,6 +412,8 @@ resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { // azure system role for setting up acr pull access var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') +// azure system role for granting push access +var acrPushRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8311e382-0749-4cb8-b61a-304f252e45ec') // azure system role for setting secret access var kvSecretUserRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // azure system role for setting storage queue access @@ -576,22 +578,6 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { } } -resource containerRegistryUsernameSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { - parent: keyVault - name: 'container-registry-username' - properties: { - value: containerRegistry.listCredentials().username - } -} - -resource containerRegistryPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { - parent: keyVault - name: 'container-registry-password' - properties: { - value: containerRegistry.listCredentials().passwords[0].value - } -} - // If we're creating the staging environment, also create a dev key vault resource devKeyVault 'Microsoft.KeyVault/vaults@2022-07-01' = if (aspnetcoreEnvironment == 'Staging') { name: devKeyVaultName @@ -813,3 +799,14 @@ resource deploymentKeyVaultUser 'Microsoft.Authorization/roleAssignments@2022-04 deploymentKeyVaultReader ] } + +// Give the PCS Deployment MI the ACR Push role to be able to push docker images +resource deploymentAcrPush 'Microsoft.Authorization/roleAssignment@2022-04-01' = { + scope: containerRegistry + name: guid(subscription().id, resourceGroup().id, 'deploymentAcrPush') + properties: { + roleDefinitionId: acrPushRole + principalType: 'ServicePrincipal' + principalId: deploymentIdentity.properties.principalId + } +} diff --git a/eng/templates/stages/deploy.yaml b/eng/templates/stages/deploy.yaml index b366b7cdea..acf8e1f63f 100644 --- a/eng/templates/stages/deploy.yaml +++ b/eng/templates/stages/deploy.yaml @@ -194,33 +194,15 @@ stages: az login --service-principal -u "$(GetAuthInfo.ServicePrincipalId)" --federated-token "$(GetAuthInfo.FederatedToken)" --tenant "$(GetAuthInfo.TenantId)" --allow-no-subscriptions .\darc\darc.exe get-default-channels --source-repo arcade-services --ci --bar-uri "$(GetAuthInfo.BarUri)" --debug - displayName: Test Azure CLI authentication - continueOnError: true + displayName: Test Azure CLI auth - powershell: .\darc\darc.exe get-default-channels --source-repo arcade-services --ci -t "$(GetAuthInfo.FederatedToken)" --bar-uri "$(GetAuthInfo.BarUri)" --debug - displayName: Test Federated token authentication - - - ${{ if in(variables['Build.SourceBranch'], 'refs/heads/main', 'refs/heads/production') }}: - - task: AzureCLI@2 - displayName: Test Darc add-build-to-channel - inputs: - azureSubscription: "Darc: Maestro Production" - scriptType: ps - scriptLocation: inlineScript - inlineScript: | - $darcBuild = .\darc\darc.exe get-build ` - --repo "https://github.com/dotnet/arcade-services" ` - --commit "$(Build.SourceVersion)" ` - --ci ` - --output-format json | - ConvertFrom-Json - - .\darc\darc.exe add-build-to-channel ` - --id $darcBuild[0].id ` - --channel "General Testing" ` - --ci ` - --azdev-pat $(dn-bot-dnceng-build-rw-code-rw-release-rw) + displayName: Test Federated token auth + + - powershell: + .\darc\darc.exe get-default-channels --source-repo arcade-services --ci --password "$(scenario-test-maestro-token)" --bar-uri "$(GetAuthInfo.BarUri)" --debug + displayName: Test BAR token auth - task: VSTest@2 displayName: Maestro Scenario Tests diff --git a/global.json b/global.json index a902b8e8af..5609ea6336 100644 --- a/global.json +++ b/global.json @@ -15,6 +15,6 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24367.1" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24376.1" } } diff --git a/src/Maestro/DependencyUpdater/.config/settings.Development.json b/src/Maestro/DependencyUpdater/.config/settings.Development.json index c84178fb69..5b942f1a65 100644 --- a/src/Maestro/DependencyUpdater/.config/settings.Development.json +++ b/src/Maestro/DependencyUpdater/.config/settings.Development.json @@ -13,14 +13,8 @@ "UseAzCliAuthentication": true }, "AzureDevOps": { - "dnceng": { - "Token": "[vault(dn-bot-dnceng-build-rw-code-rw-release-rw)]" - }, - "devdiv": { - "Token": "[vault(dn-bot-devdiv-build-rw-code-rw-release-rw)]" - }, - "domoreexp": { - "Token": "[vault(dn-bot-domoreexp-build-rw-code-rw-release-rw)]" + "default": { + "UseLocalCredentials": true } } -} \ No newline at end of file +} diff --git a/src/Maestro/FeedCleanerService/.config/settings.Development.json b/src/Maestro/FeedCleanerService/.config/settings.Development.json index b2a3eca14b..d8f688287d 100644 --- a/src/Maestro/FeedCleanerService/.config/settings.Development.json +++ b/src/Maestro/FeedCleanerService/.config/settings.Development.json @@ -8,8 +8,8 @@ "ConnectionString": "Data Source=localhost\\SQLEXPRESS;Initial Catalog=BuildAssetRegistry;Integrated Security=true" }, "AzureDevOps": { - "dnceng": { - "Token": "[vault(dn-bot-dnceng-packaging-rwm)]" + "default": { + "UseLocalCredentials": true } } -} \ No newline at end of file +} diff --git a/src/Maestro/Maestro.Authentication/AuthenticationConfiguration.cs b/src/Maestro/Maestro.Authentication/AuthenticationConfiguration.cs index 4903c5be68..06912d5f65 100644 --- a/src/Maestro/Maestro.Authentication/AuthenticationConfiguration.cs +++ b/src/Maestro/Maestro.Authentication/AuthenticationConfiguration.cs @@ -2,23 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; using Maestro.Data; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.DotNet.Web.Authentication; using Microsoft.DotNet.Web.Authentication.AccessToken; using Microsoft.DotNet.Web.Authentication.GitHub; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Microsoft.Identity.Web; #nullable enable @@ -26,233 +20,93 @@ namespace Maestro.Authentication; public static class AuthenticationConfiguration { - public const string GitHubScheme = "GitHub"; - + public const string EntraAuthorizationPolicyName = "Entra"; public const string MsftAuthorizationPolicyName = "msft"; - public static readonly TimeSpan LoginCookieLifetime = TimeSpan.FromMinutes(30); - public const string AccountSignInRoute = "/Account/SignIn"; + public static readonly string[] AuthenticationSchemes = + [ + EntraAuthorizationPolicyName, + OpenIdConnectDefaults.AuthenticationScheme, + PersonalAccessTokenDefaults.AuthenticationScheme, + ]; + /// /// Sets up authentication and authorization services. /// - /// Should we require the @dotnet/dnceng or @dotnet/arcade-cotrib team? - /// GitHub auth configuration /// Path of the URI for which we require auth (e.g. "/api") /// Entra-based auth configuration (or null if turned off) public static void ConfigureAuthServices( this IServiceCollection services, - bool requirePolicyRole, - IConfigurationSection githubAuthConfig, string authenticationSchemeRequestPath, - IConfigurationSection? entraAuthConfig = null) + IConfigurationSection? entraAuthConfig) { + services.Configure(options => + { + // This lambda determines whether user consent for non-essential cookies is needed for a given request. + options.CheckConsentNeeded = context => true; + options.MinimumSameSitePolicy = SameSiteMode.Unspecified; + // Handling SameSite cookie according to https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1 + options.HandleSameSiteCookieCompatibility(); + }); + + // Support for old Maestro tokens services .AddIdentity>( options => options.Lockout.AllowedForNewUsers = false) .AddEntityFrameworkStores(); - var authentication = services - .AddAuthentication(options => - { - options.DefaultAuthenticateScheme = options.DefaultChallengeScheme = options.DefaultScheme = "Contextual"; - options.DefaultSignInScheme = IdentityConstants.ExternalScheme; - }) - .AddPolicyScheme("Contextual", "Contextual", policyOptions => - { - policyOptions.ForwardDefaultSelector = ctx => - { - if (!ctx.Request.Path.StartsWithSegments(authenticationSchemeRequestPath)) - { - return IdentityConstants.ApplicationScheme; - } - - // This is a really simple and a bit hacky (but temporary) quick way to tell between BAR and Entra tokens - string? authHeader = ctx.Request.Headers.Authorization.FirstOrDefault(); - return authHeader?.Length > 100 && authHeader.ToLower().StartsWith("bearer ey") - ? JwtBearerDefaults.AuthenticationScheme - : PersonalAccessTokenDefaults.AuthenticationScheme; - }; - }); - - if (entraAuthConfig?.Exists() ?? false) + // Register Entra based authentication + if (!entraAuthConfig.Exists()) { - authentication - .AddMicrosoftIdentityWebApi(entraAuthConfig, subscribeToJwtBearerMiddlewareDiagnosticsEvents: true); + throw new Exception("Entra authentication is missing in configuration"); } - authentication - .AddPersonalAccessToken( - options => - { - options.Events = new PersonalAccessTokenEvents - { - OnSetTokenHash = async context => - { - var dbContext = context.HttpContext.RequestServices - .GetRequiredService(); - int userId = context.User.Id; - var token = new ApplicationUserPersonalAccessToken - { - ApplicationUserId = userId, - Name = context.Name, - Hash = context.Hash, - Created = DateTimeOffset.UtcNow - }; - await dbContext.Set().AddAsync(token); - await dbContext.SaveChangesAsync(); + string entraRole = entraAuthConfig["UserRole"] + ?? throw new Exception("Expected 'UserRole' to be set in the Entra configuration containing " + + "a role on the application granted to API users"); - return token.Id; - }, - OnGetTokenHash = async context => - { - var dbContext = context.HttpContext.RequestServices - .GetRequiredService(); - ApplicationUserPersonalAccessToken? token = await dbContext - .Set() - .Where(t => t.Id == context.TokenId) - .Include(t => t.ApplicationUser) - .FirstOrDefaultAsync(); - if (token != null) - { - context.Success(token.Hash, token.ApplicationUser); - } - }, - OnValidatePrincipal = async context => - { - ApplicationUser user = context.User; - var dbContext = context.HttpContext.RequestServices - .GetRequiredService(); - var userManager = context.HttpContext.RequestServices - .GetRequiredService>(); - var signInManager = context.HttpContext.RequestServices - .GetRequiredService>(); - var gitHubClaimResolver = context.HttpContext.RequestServices - .GetRequiredService(); + var openIdAuth = services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme); - await UpdateUserIfNeededAsync(user, dbContext, userManager, gitHubClaimResolver); + openIdAuth + .AddMicrosoftIdentityWebApi(entraAuthConfig, EntraAuthorizationPolicyName); - ClaimsPrincipal principal = await signInManager.CreateUserPrincipalAsync(user); - context.ReplacePrincipal(principal); - } - }; - }) - .AddGitHubOAuth(githubAuthConfig, GitHubScheme); + openIdAuth + .AddMicrosoftIdentityWebApp(entraAuthConfig); - - services.ConfigureExternalCookie( - options => - { - options.ExpireTimeSpan = TimeSpan.FromMinutes(30); - options.ReturnUrlParameter = "returnUrl"; - options.LoginPath = AccountSignInRoute; - options.Events = new CookieAuthenticationEvents - { - OnRedirectToLogin = ctx => - { - if (ctx.Request.Path.StartsWithSegments("/api")) - { - ctx.Response.StatusCode = 401; - return Task.CompletedTask; - } - - ctx.Response.Redirect(ctx.RedirectUri); - return Task.CompletedTask; - }, - OnRedirectToAccessDenied = ctx => - { - ctx.Response.StatusCode = 403; - return Task.CompletedTask; - }, - }; - }); - services.ConfigureApplicationCookie( - options => + var authentication = services + .AddAuthentication(options => { - options.ExpireTimeSpan = LoginCookieLifetime; - options.SlidingExpiration = true; - options.ReturnUrlParameter = "returnUrl"; - options.LoginPath = AccountSignInRoute; - options.Events = new CookieAuthenticationEvents - { - OnSigningIn = async ctx => - { - var dbContext = ctx.HttpContext.RequestServices - .GetRequiredService(); - var signInManager = ctx.HttpContext.RequestServices - .GetRequiredService>(); - var userManager = ctx.HttpContext.RequestServices - .GetRequiredService>(); - ExternalLoginInfo info = await signInManager.GetExternalLoginInfoAsync(); - - var user = await userManager.GetUserAsync(ctx.Principal); - await UpdateUserTokenAsync(dbContext, userManager, user, info); - - IdentityOptions identityOptions = ctx.HttpContext.RequestServices - .GetRequiredService>() - .Value; - - // replace the ClaimsPrincipal we are about to serialize to the cookie with a reference - Claim claim = ctx.Principal!.Claims.First( - c => c.Type == identityOptions.ClaimsIdentity.UserIdClaimType); - Claim[] claims = [claim]; - var identity = new ClaimsIdentity(claims, IdentityConstants.ApplicationScheme); - ctx.Principal = new ClaimsPrincipal(identity); - }, - OnValidatePrincipal = async ctx => - { - var dbContext = ctx.HttpContext.RequestServices - .GetRequiredService(); - var userManager = ctx.HttpContext.RequestServices - .GetRequiredService>(); - var signInManager = ctx.HttpContext.RequestServices - .GetRequiredService>(); - var gitHubClaimResolver = ctx.HttpContext.RequestServices - .GetRequiredService(); - - // extract the userId from the ClaimsPrincipal and read the user from the Db - ApplicationUser user = await userManager.GetUserAsync(ctx.Principal); - if (user == null) - { - ctx.RejectPrincipal(); - } - else - { - await UpdateUserIfNeededAsync(user, dbContext, userManager, gitHubClaimResolver); - - ClaimsPrincipal principal = await signInManager.CreateUserPrincipalAsync(user); - ctx.ReplacePrincipal(principal); - } - } - }; + options.DefaultSignInScheme = OpenIdConnectDefaults.AuthenticationScheme; }); - // While entra is optional, we only verify the role when it's available in configuration - // When it's disabled, we create a random GUID policy that will be never satisfied - string entraRole = entraAuthConfig?.Exists() ?? false - ? entraAuthConfig["UserRole"] ?? throw new Exception("Expected 'UserRole' to be set in the AzureAd configuration - " + - "a role on the application granted to API users") - : Guid.NewGuid().ToString(); + // Register support for BAR token validation + authentication.AddScheme, BarTokenAuthenticationHandler>( + PersonalAccessTokenDefaults.AuthenticationScheme, + configureOptions: null); - services.AddAuthorization( - options => + services + .AddAuthorization(options => { - options.AddPolicy( - MsftAuthorizationPolicyName, - policy => + options.AddPolicy(MsftAuthorizationPolicyName, policy => + { + // These roles are still needed for the BAR token validation + // When we deprecate the BAR token, we can remove these and keep the entra role validation only + var dncengRole = GitHubClaimResolver.GetTeamRole("dotnet", "dnceng"); + var arcadeContribRole = GitHubClaimResolver.GetTeamRole("dotnet", "arcade-contrib"); + var prodconSvcsRole = GitHubClaimResolver.GetTeamRole("dotnet", "prodconsvcs"); + + policy.AddAuthenticationSchemes(AuthenticationSchemes); + policy.RequireAuthenticatedUser(); + policy.RequireAssertion(context => { - policy.RequireAuthenticatedUser(); - if (requirePolicyRole) - { - var dncengRole = GitHubClaimResolver.GetTeamRole("dotnet", "dnceng"); - var arcadeContribRole = GitHubClaimResolver.GetTeamRole("dotnet", "arcade-contrib"); - - policy.RequireAssertion(context => context.User.IsInRole(entraRole) - || context.User.IsInRole(dncengRole) - || context.User.IsInRole(arcadeContribRole)); - } + return context.User.IsInRole(entraRole) + || context.User.IsInRole(dncengRole) + || context.User.IsInRole(arcadeContribRole) + || context.User.IsInRole(prodconSvcsRole); }); + }); }); services.Configure( @@ -261,93 +115,4 @@ public static void ConfigureAuthServices( options.Conventions.Add(new DefaultAuthorizeActionModelConvention(MsftAuthorizationPolicyName)); }); } - - public static bool ShouldAddClaimToUser(Claim c) - { - return c.Type == ClaimTypes.Email || c.Type == "urn:github:name" || c.Type == "urn:github:url" || c.Type == ClaimTypes.Role; - } - - private static async Task UpdateUserTokenAsync(BuildAssetRegistryContext dbContext, - UserManager userManager, ApplicationUser user, ExternalLoginInfo info) - { - try - { - await userManager.SetAuthenticationTokenAsync( - user, - info.LoginProvider, - "access_token", - info.AuthenticationTokens.First(t => t.Name == "access_token").Value); - } - catch (DbUpdateConcurrencyException) - { - // If we have a concurrent modification exception that means another request updated this token, we can abandon our update and reload the data from the DB - foreach (EntityEntry entry in dbContext.ChangeTracker.Entries()) - { - await entry.ReloadAsync(); - } - } - } - - private static async Task UpdateUserIfNeededAsync( - ApplicationUser user, - BuildAssetRegistryContext dbContext, - UserManager userManager, - GitHubClaimResolver gitHubClaimResolver) - { - while (true) - { - try - { - if (ShouldUpdateUser(user)) - { - await UpdateUserAsync(user, dbContext, userManager, gitHubClaimResolver); - } - - break; - } - catch (DbUpdateConcurrencyException) - { - // If we have a concurrent modification exception reload the data from the DB and try again - foreach (EntityEntry entry in dbContext.ChangeTracker.Entries()) - { - await entry.ReloadAsync(); - } - } - } - } - - private static bool ShouldUpdateUser(ApplicationUser user) - { - // If we haven't updated the user in the last 30 minutes - return DateTimeOffset.UtcNow - user.LastUpdated > new TimeSpan(0, 30, 0); - } - - private static async Task UpdateUserAsync( - ApplicationUser user, - BuildAssetRegistryContext dbContext, - UserManager userManager, - GitHubClaimResolver gitHubClaimResolver) - { - await dbContext.Database.CreateExecutionStrategy().ExecuteAsync(async () => - { - using (IDbContextTransaction txn = await dbContext.Database.BeginTransactionAsync()) - { - string token = await userManager.GetAuthenticationTokenAsync(user, GitHubScheme, "access_token"); - var newClaims = (await gitHubClaimResolver.GetUserInformationClaims(token)).Concat( - await gitHubClaimResolver.GetMembershipClaims(token) - ).Where(ShouldAddClaimToUser); - var currentClaims = (await userManager.GetClaimsAsync(user)).ToList(); - - // remove old claims - await userManager.RemoveClaimsAsync(user, currentClaims); - - // add new claims - await userManager.AddClaimsAsync(user, newClaims); - - user.LastUpdated = DateTimeOffset.UtcNow; - await dbContext.SaveChangesAsync(); - txn.Commit(); - } - }); - } } diff --git a/src/Maestro/Maestro.Authentication/BarTokenAuthenticationHandler.cs b/src/Maestro/Maestro.Authentication/BarTokenAuthenticationHandler.cs new file mode 100644 index 0000000000..4fc3782e95 --- /dev/null +++ b/src/Maestro/Maestro.Authentication/BarTokenAuthenticationHandler.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Net; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Maestro.Data; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.DotNet.Web.Authentication.AccessToken; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +#nullable enable +namespace Maestro.Authentication; + +public class BarTokenAuthenticationHandler : AuthenticationHandler> +{ + private readonly BuildAssetRegistryContext _dbContext; + private readonly SignInManager _signInManager; + private readonly IPasswordHasher _passwordHasher; + private readonly ILogger _logger; + + public BarTokenAuthenticationHandler( + BuildAssetRegistryContext context, + SignInManager signInManager, + IPasswordHasher passwordHasher, + IOptionsMonitor> options, + ILoggerFactory loggerFactory, + UrlEncoder encoder, + ISystemClock clock, + ILogger logger) + : base(options, loggerFactory, encoder, clock) + { + _dbContext = context; + _signInManager = signInManager; + _passwordHasher = passwordHasher; + _logger = logger; + } + + protected override async Task HandleAuthenticateAsync() + { + try + { + string? requestToken = GetToken(); + if (string.IsNullOrEmpty(requestToken)) + { + return AuthenticateResult.NoResult(); + } + + (int tokenId, string password)? decodedToken = DecodeToken(requestToken); + + if (!decodedToken.HasValue || decodedToken.Value.tokenId == 0 || string.IsNullOrEmpty(decodedToken.Value.password)) + { + string message = "Failed to decode personal access token"; + _logger.LogInformation(message); + return AuthenticateResult.Fail(message); + } + + (int tokenId, string password) = decodedToken.Value; + + ApplicationUserPersonalAccessToken? dbToken = await _dbContext + .Set() + .Where(t => t.Id == tokenId) + .Include(t => t.ApplicationUser) + .FirstOrDefaultAsync(); + + if (dbToken == null) + { + return AuthenticateResult.Fail("No existing token found"); + } + + string hash = _passwordHasher.HashPassword(dbToken.ApplicationUser, password); + PasswordVerificationResult result = _passwordHasher.VerifyHashedPassword(dbToken.ApplicationUser, hash, password); + + if (result != PasswordVerificationResult.Success && result != PasswordVerificationResult.SuccessRehashNeeded) + { + return AuthenticateResult.Fail("Invalid personal access token password"); + } + + var ticket = new AuthenticationTicket(await _signInManager.CreateUserPrincipalAsync(dbToken.ApplicationUser), Scheme.Name); + var userContext = new PersonalAccessTokenValidatePrincipalContext(Context, Scheme, Options, ticket, dbToken.ApplicationUser); + if (userContext.Principal == null) + { + return AuthenticateResult.Fail("No principal found"); + } + + return AuthenticateResult.Success(new AuthenticationTicket(userContext.Principal, userContext.Properties, Scheme.Name)); + } + catch (Exception e) + { + _logger.LogDebug(e, "Failed to authenticate personal access token"); + } + + return AuthenticateResult.NoResult(); + } + + private string? GetToken() + { + string? authHeader = Request.Headers["Authorization"]; + if (!string.IsNullOrEmpty(authHeader)) + { + string prefix = "Bearer "; + if (authHeader.StartsWith(prefix)) + { + authHeader = authHeader.Substring(prefix.Length).Trim(); + } + } + + return authHeader; + } + + private (int tokenId, string password)? DecodeToken(string input) + { + byte[] tokenBytes = WebEncoders.Base64UrlDecode(input); + if (tokenBytes.Length != PersonalAccessTokenUtilities.CalculateTokenSizeForPasswordSize(16)) + { + return null; + } + + int tokenId = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(tokenBytes, 0)); + string password = WebEncoders.Base64UrlEncode(tokenBytes, PersonalAccessTokenUtilities.TokenIdByteCount, Options.PasswordSize); + return (tokenId, password); + } +} diff --git a/src/Maestro/Maestro.Authentication/Maestro.Authentication.csproj b/src/Maestro/Maestro.Authentication/Maestro.Authentication.csproj index d878f5bfc4..37a137ddbf 100644 --- a/src/Maestro/Maestro.Authentication/Maestro.Authentication.csproj +++ b/src/Maestro/Maestro.Authentication/Maestro.Authentication.csproj @@ -5,8 +5,6 @@ - - diff --git a/src/Maestro/Maestro.Common/AppCredentials/CredentialResolverOptions.cs b/src/Maestro/Maestro.Common/AppCredentials/CredentialResolverOptions.cs index 2034bece49..25871eb8c7 100644 --- a/src/Maestro/Maestro.Common/AppCredentials/CredentialResolverOptions.cs +++ b/src/Maestro/Maestro.Common/AppCredentials/CredentialResolverOptions.cs @@ -24,4 +24,9 @@ public class CredentialResolverOptions /// Managed Identity to use for the auth /// public string? ManagedIdentityId { get; set; } + + /// + /// Whether to use local credentials + /// + public bool UseLocalCredentials { get; set; } } diff --git a/src/Maestro/Maestro.Common/AzureDevOpsTokens/AzureDevOpsTokenProvider.cs b/src/Maestro/Maestro.Common/AzureDevOpsTokens/AzureDevOpsTokenProvider.cs index 0ada4cacfe..0e871de32b 100644 --- a/src/Maestro/Maestro.Common/AzureDevOpsTokens/AzureDevOpsTokenProvider.cs +++ b/src/Maestro/Maestro.Common/AzureDevOpsTokens/AzureDevOpsTokenProvider.cs @@ -6,6 +6,7 @@ using Azure.Identity; using Maestro.Common.AppCredentials; using Microsoft.Extensions.Options; +using Microsoft.DncEng.Configuration.Extensions; namespace Maestro.Common.AzureDevOpsTokens; @@ -111,13 +112,20 @@ private static Dictionary GetCredentials( var account = pair.Key; var option = pair.Value; - // 1. AzDO PAT from configuration + // 0. AzDO PAT from configuration if (!string.IsNullOrEmpty(option.Token)) { credentials[account] = new ResolvingCredential((context, cancellationToken) => patResolver(account, context, cancellationToken)); continue; } + // 1. Use localy-cached credentials for development flows + if (option.UseLocalCredentials) + { + credentials[account] = new LocalDevTokenCredential(); + continue; + } + // 2. Federated token that can be used to fetch an app token (for CI scenarios) if (!string.IsNullOrEmpty(option.FederatedToken)) { diff --git a/src/Maestro/Maestro.Common/Maestro.Common.csproj b/src/Maestro/Maestro.Common/Maestro.Common.csproj index e08e86ecc6..c4db981954 100644 --- a/src/Maestro/Maestro.Common/Maestro.Common.csproj +++ b/src/Maestro/Maestro.Common/Maestro.Common.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Maestro/Maestro.Web/.config/settings.Development.json b/src/Maestro/Maestro.Web/.config/settings.Development.json index 6f961d63d9..404a617790 100644 --- a/src/Maestro/Maestro.Web/.config/settings.Development.json +++ b/src/Maestro/Maestro.Web/.config/settings.Development.json @@ -8,7 +8,9 @@ "BuildAssetRegistry": { "ConnectionString": "Data Source=localhost\\SQLEXPRESS;Initial Catalog=BuildAssetRegistry;Integrated Security=true" }, - "ForceLocalApi": false, + "ApiRedirect": { + "Uri": "https://maestro.dot.net/" + }, "DataProtection": { "KeyFileUri": "", "KeyIdentifier": "" @@ -18,15 +20,14 @@ "KustoClusterUri": "https://engdata.westus2.kusto.windows.net", "UseAzCliAuthentication": true }, + "EntraAuthentication": { + // https://ms.portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/baf98f1b-374e-487d-af42-aa33807f11e4/objectId/a0e22263-aa27-4dc8-81d6-f12e63fb0d96/isMSAApp~/false/defaultBlade/Overview/appSignInAudience/AzureADMyOrg/servicePrincipalCreated~/true + "ClientId": "baf98f1b-374e-487d-af42-aa33807f11e4", + "Scope": [ "api://baf98f1b-374e-487d-af42-aa33807f11e4/Maestro.User" ] + }, "AzureDevOps": { - "dnceng": { - "Token": "[vault(dn-bot-dnceng-build-rw-code-rw-release-rw)]" - }, - "devdiv": { - "Token": "[vault(dn-bot-devdiv-build-rw-code-rw-release-rw)]" - }, - "domoreexp": { - "Token": "[vault(dn-bot-domoreexp-build-rw-code-rw-release-rw)]" + "default": { + "UseLocalCredentials": true } } -} \ No newline at end of file +} diff --git a/src/Maestro/Maestro.Web/.config/settings.Production.json b/src/Maestro/Maestro.Web/.config/settings.Production.json index d3e979fcb6..f232949486 100644 --- a/src/Maestro/Maestro.Web/.config/settings.Production.json +++ b/src/Maestro/Maestro.Web/.config/settings.Production.json @@ -5,10 +5,6 @@ }, "KeyVaultUri": "https://maestroprod.vault.azure.net/", "AppConfigurationUri": "https://maestroprod.azconfig.io/", - "ApiRedirect": { - "uri": "", - "token": "" - }, "DataProtection": { "KeyBlobUri": "https://maestroprod1337.blob.core.windows.net/dataprotection/keys.xml", "DataProtectionKeyUri": "https://maestroprod.vault.azure.net/keys/data-protection-encryption-key/" @@ -21,12 +17,9 @@ "KustoClusterUri": "https://engsrvprod.westus.kusto.windows.net" }, "EntraAuthentication": { - "Instance": "https://login.microsoftonline.com/", - "TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", // https://ms.portal.azure.com/?l=en.en-us#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/54c17f3d-7325-4eca-9db7-f090bfc765a8/objectId/2ed47e8b-af61-4f2c-917b-0cb3d00d3593/isMSAApp~/false/defaultBlade/Overview/appSignInAudience/AzureADMyOrg/servicePrincipalCreated~/true "ClientId": "54c17f3d-7325-4eca-9db7-f090bfc765a8", - "UserRole": "User", - "RedirectUri": "https://maestro.dot.net/signin-oidc" + "Scope": [ "api://54c17f3d-7325-4eca-9db7-f090bfc765a8/Maestro.User" ] }, "AzureDevOps": { "default": { diff --git a/src/Maestro/Maestro.Web/.config/settings.Staging.json b/src/Maestro/Maestro.Web/.config/settings.Staging.json index 796b839997..3d5e925add 100644 --- a/src/Maestro/Maestro.Web/.config/settings.Staging.json +++ b/src/Maestro/Maestro.Web/.config/settings.Staging.json @@ -5,6 +5,10 @@ }, "KeyVaultUri": "https://maestroint.vault.azure.net/", "AppConfigurationUri": "https://maestroint.azconfig.io/", + "ApiRedirect": { + "Uri": "https://maestro.dot.net/", + "ManagedIdentityClientId": "system" + }, "DataProtection": { "KeyBlobUri": "https://maestroint.blob.core.windows.net/dataprotection/keys.xml", "DataProtectionKeyUri": "https://maestroint.vault.azure.net/keys/data-protection-encryption-key/" @@ -17,12 +21,9 @@ "KustoClusterUri": "https://engdata.westus2.kusto.windows.net" }, "EntraAuthentication": { - "Instance": "https://login.microsoftonline.com/", - "TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", // https://ms.portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/baf98f1b-374e-487d-af42-aa33807f11e4/objectId/a0e22263-aa27-4dc8-81d6-f12e63fb0d96/isMSAApp~/false/defaultBlade/Overview/appSignInAudience/AzureADMyOrg/servicePrincipalCreated~/true "ClientId": "baf98f1b-374e-487d-af42-aa33807f11e4", - "UserRole": "User", - "RedirectUri": "https://maestro.int-dot.net/signin-oidc" + "Scope": [ "api://baf98f1b-374e-487d-af42-aa33807f11e4/Maestro.User" ] }, "AzureDevOps": { "default": { diff --git a/src/Maestro/Maestro.Web/.config/settings.json b/src/Maestro/Maestro.Web/.config/settings.json index 85a503c094..d2caf9d319 100644 --- a/src/Maestro/Maestro.Web/.config/settings.json +++ b/src/Maestro/Maestro.Web/.config/settings.json @@ -1,12 +1,4 @@ { - "GitHubAuthentication": { - "ClientId": "[vault(github-oauth-id)]", - "ClientSecret": "[vault(github-oauth-secret)]", - "SaveTokens": true, - "CallbackPath": "/signin/github", - "UserAgentProduct": "", - "ClaimsIssuer": "github" - }, "GitHub": { "GitHubAppId": "[vault(github-app-id)]", "PrivateKey": "[vault(github-app-private-key)]" @@ -18,9 +10,12 @@ } } }, - "ApiRedirect": { - "uri": "https://maestro.dot.net/", - "token": "[vault(prod-maestro-token)]" + "EntraAuthentication": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", + "UserRole": "User", + "CallbackPath": "/signin-oidc", + "SignedOutCallbackPath": "/signout-callback-oidc" }, "GitDownloadLocation": "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/git/Git-2.32.0-64-bit.zip", "EnableAutoBuildPromotion": "[config(FeatureManagement:AutoBuildPromotion)]" diff --git a/src/Maestro/Maestro.Web/Helpers.cs b/src/Maestro/Maestro.Web/Helpers.cs index 72a41b707f..10e234925c 100644 --- a/src/Maestro/Maestro.Web/Helpers.cs +++ b/src/Maestro/Maestro.Web/Helpers.cs @@ -47,7 +47,14 @@ public static async Task ProxyRequestAsync(this HttpContext conte case "content-type": continue; default: - req.Headers.Add(key, values.ToArray()); + try + { + req.Headers.Add(key, values.ToArray()); + } + catch + { + // Some headers set by the client might be invalid (e.g. contain :) + } break; } } diff --git a/src/Maestro/Maestro.Web/Maestro.Web.csproj b/src/Maestro/Maestro.Web/Maestro.Web.csproj index 1b27380b23..fb94c9d306 100644 --- a/src/Maestro/Maestro.Web/Maestro.Web.csproj +++ b/src/Maestro/Maestro.Web/Maestro.Web.csproj @@ -25,8 +25,6 @@ - - diff --git a/src/Maestro/Maestro.Web/Pages/Account/AccountController.cs b/src/Maestro/Maestro.Web/Pages/Account/AccountController.cs index 2e5d352d8c..a6576e447c 100644 --- a/src/Maestro/Maestro.Web/Pages/Account/AccountController.cs +++ b/src/Maestro/Maestro.Web/Pages/Account/AccountController.cs @@ -1,151 +1,31 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Maestro.Authentication; -using Maestro.Data; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; -using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; namespace Maestro.Web.Pages.Account; public class AccountController : Controller { - public AccountController( - SignInManager signInManager, - UserManager userManager, - BuildAssetRegistryContext context) - { - SignInManager = signInManager; - UserManager = userManager; - Context = context; - } - - public SignInManager SignInManager { get; } - public UserManager UserManager { get; } - public BuildAssetRegistryContext Context { get; } - [HttpGet("/Account/SignOut")] [AllowAnonymous] public new async Task SignOut() { - await SignInManager.SignOutAsync(); - return RedirectToPage("/Index"); + await HttpContext.SignOutAsync(); + return RedirectToPage("/"); } [HttpGet(AuthenticationConfiguration.AccountSignInRoute)] [AllowAnonymous] public IActionResult SignIn(string returnUrl = null) { - string redirectUrl = Url.Action(nameof(LogInCallback), "Account", new {returnUrl}); - AuthenticationProperties properties = - SignInManager.ConfigureExternalAuthenticationProperties(AuthenticationConfiguration.GitHubScheme, redirectUrl); - return Challenge(properties, AuthenticationConfiguration.GitHubScheme); - } - - [HttpGet("/Account/LogInCallback")] - [AllowAnonymous] - public async Task LogInCallback(string returnUrl = null, string remoteError = null) - { - if (remoteError != null) - { - return StatusCode(400, $"Error loging in: {remoteError}"); - } - - ExternalLoginInfo info = await SignInManager.GetExternalLoginInfoAsync(); - if (info == null) - { - return StatusCode(400, "Unable to Sign In"); - } - - SignInResult signInResult = - await SignInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, true); // lgtm [cs/user-controlled-bypass] Safe in this context, failure will block user from sign-in - if (signInResult.Succeeded) - { - return RedirectToLocal(returnUrl); - } - - if (!signInResult.IsLockedOut) - { - ApplicationUser user = await CreateUserAsync(info); - if (user != null) - { - await SignInManager.SignInAsync(user, true); - await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); - return RedirectToLocal(returnUrl); - } - } - - return StatusCode(403); - } - - private async Task CreateUserAsync(ExternalLoginInfo info) - { - string id = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier); - string name = info.Principal.FindFirstValue(ClaimTypes.Name); - string fullName = info.Principal.FindFirstValue("urn:github:name") ?? name; - string accessToken = info.AuthenticationTokens.First(t => t.Name == "access_token").Value; - - return await Context.Database.CreateExecutionStrategy().ExecuteAsync(async () => - { - using (IDbContextTransaction txn = await Context.Database.BeginTransactionAsync()) - { - var user = new ApplicationUser - { - UserName = name, - FullName = fullName - }; - IdentityResult result = await UserManager.CreateAsync(user); - if (!result.Succeeded) - { - return null; - } - - result = await UserManager.SetAuthenticationTokenAsync( - user, - info.LoginProvider, - "access_token", - accessToken); - if (!result.Succeeded) - { - return null; - } - - IEnumerable claimsToAdd = info.Principal.Claims.Where(AuthenticationConfiguration.ShouldAddClaimToUser); - - result = await UserManager.AddClaimsAsync(user, claimsToAdd); - if (!result.Succeeded) - { - return null; - } - - result = await UserManager.AddLoginAsync(user, info); - if (!result.Succeeded) - { - return null; - } - - txn.Commit(); - return user; - } - }); - } - - private IActionResult RedirectToLocal(string returnUrl) - { - if (Url.IsLocalUrl(returnUrl)) - { - return Redirect(returnUrl); - } - - return Redirect("/"); + return Challenge( + new AuthenticationProperties() { RedirectUri = "/" }, + OpenIdConnectDefaults.AuthenticationScheme); } } diff --git a/src/Maestro/Maestro.Web/Pages/Account/Tokens.cshtml b/src/Maestro/Maestro.Web/Pages/Account/Tokens.cshtml deleted file mode 100644 index a8ed30f011..0000000000 --- a/src/Maestro/Maestro.Web/Pages/Account/Tokens.cshtml +++ /dev/null @@ -1,82 +0,0 @@ -@page -@using Maestro.Web.Pages.Account -@model Maestro.Web.Pages.Account.TokensModel -@{ - ViewBag.Title = "Tokens"; -} - - -
- Error: @Model.Error -
-
- @Model.Message -
-
-
Tokens
-
-
-
- You Have No Tokens, Create One. -
-
- @Model.CreatedTokenValue 📋 - -
- @foreach (TokensModel.TokenModel token in Model.Tokens) - { - if (token.Id == Model.CreatedTokenId) - { - continue; - } -
-
-
- - -
-
- @token.Name created @token.Created.Humanize() -
- } -
-
-
-
-
Create Token
-
-
-
- - -
- A token name is required. -
-
- -
-
-
diff --git a/src/Maestro/Maestro.Web/Pages/Account/Tokens.cshtml.cs b/src/Maestro/Maestro.Web/Pages/Account/Tokens.cshtml.cs deleted file mode 100644 index 4d28541746..0000000000 --- a/src/Maestro/Maestro.Web/Pages/Account/Tokens.cshtml.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Threading.Tasks; -using Maestro.Data; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.DotNet.Web.Authentication.AccessToken; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace Maestro.Web.Pages.Account; - -public class TokensModel : PageModel -{ - public TokensModel( - BuildAssetRegistryContext context, - UserManager userManager, - ILogger logger) - { - Context = context; - UserManager = userManager; - Logger = logger; - } - - public BuildAssetRegistryContext Context { get; } - public UserManager UserManager { get; } - public ILogger Logger { get; } - - public List Tokens { get; private set; } - - [BindProperty] - public string TokenName { get; set; } - - [BindProperty] - public int TokenId { get; set; } - - [TempData] - public int CreatedTokenId { get; set; } - - [TempData] - public string CreatedTokenValue { get; set; } - - [TempData] - public string Message { get; set; } - - [TempData] - public string Error { get; set; } - - public async Task OnGet() - { - ApplicationUser user = await UserManager.GetUserAsync(User); - Tokens = await GetTokens(user); - return Page(); - } - - public async Task OnPostDeleteTokenAsync() - { - if (TokenId < 1) - { - return BadRequest("Invalid Token Id"); - } - - ApplicationUser user = await UserManager.GetUserAsync(User); - try - { - ApplicationUserPersonalAccessToken token = await Context.Set() - .Where(t => t.ApplicationUserId == user.Id && t.Id == TokenId) - .SingleAsync(); - Context.Remove(token); - await Context.SaveChangesAsync(); - Message = "Token deleted."; - } - catch (Exception ex) - { - Logger.LogError(ex, "Unable to delete token."); - Error = "Unable to delete token."; - } - - return RedirectToPage(); - } - - public async Task OnPostNewTokenAsync() - { - ApplicationUser user = await UserManager.GetUserAsync(User); - try - { - (CreatedTokenId, CreatedTokenValue) = await HttpContext.CreatePersonalAccessTokenAsync(user, TokenName); - } - catch (DbUpdateException dbEx) when (dbEx.InnerException is SqlException sqlEx && - sqlEx.Message.Contains("Cannot insert duplicate key row")) - { - Error = "A token with that name already exists."; - } - catch (Exception) - { - Error = "Unable to create token."; - } - - if (Error == null) - { - Message = "Make sure to copy your new personal access token now. You won't be able to see it again!"; - } - - return RedirectToPage(); - } - - private async Task> GetTokens(ApplicationUser user) - { - await Context.Entry(user).Collection(u => u.PersonalAccessTokens).LoadAsync(); - return user.PersonalAccessTokens.Select( - t => new TokenModel - { - Name = t.Name, - Id = t.Id, - Created = t.Created - }) - .ToList(); - } - - public class TokenModel - { - public string Name { get; set; } - public int Id { get; set; } - public DateTimeOffset Created { get; set; } - } -} diff --git a/src/Maestro/Maestro.Web/Pages/Index.cshtml b/src/Maestro/Maestro.Web/Pages/Index.cshtml index 3e09a67d59..9078c33f31 100644 --- a/src/Maestro/Maestro.Web/Pages/Index.cshtml +++ b/src/Maestro/Maestro.Web/Pages/Index.cshtml @@ -84,7 +84,7 @@ "); return new HtmlString(string.Join("", scripts)); } + + private JObject ReadAssetsJson() + { + var path = Environment.EnvironmentName == "Development" && !ServiceFabricHelpers.RunningInServiceFabric() + ? Path.Join(Program.LocalCompiledStaticFilesPath, "assets.json") + : Path.Join(Environment.WebRootPath, "assets.json"); + + return JObject.Parse(System.IO.File.ReadAllText(path)); + } } diff --git a/src/Maestro/Maestro.Web/Pages/_Layout.cshtml b/src/Maestro/Maestro.Web/Pages/_Layout.cshtml index ebb81f5a6b..79bfdefeb5 100644 --- a/src/Maestro/Maestro.Web/Pages/_Layout.cshtml +++ b/src/Maestro/Maestro.Web/Pages/_Layout.cshtml @@ -83,19 +83,23 @@ @@ -128,16 +132,3 @@ - -@inject UserManager UserManager - -@functions -{ - - async Task GetUserNameAsync() - { - ApplicationUser user = await UserManager.GetUserAsync(Context.User); - return user?.FullName; - } - -} diff --git a/src/Maestro/Maestro.Web/Program.cs b/src/Maestro/Maestro.Web/Program.cs index c6c2fb9448..7362118a58 100644 --- a/src/Maestro/Maestro.Web/Program.cs +++ b/src/Maestro/Maestro.Web/Program.cs @@ -1,14 +1,33 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.IO; +using System.Linq; using Microsoft.DotNet.ServiceFabric.ServiceHost; namespace Maestro.Web; internal static class Program { + internal static int LocalHttpsPort => int.Parse(Environment.GetEnvironmentVariable("ASPNETCORE_HTTPS_PORT") ?? "443"); + + /// + /// Path to the compiled static files for the Angular app. + /// This is required when running Maestro.Web locally, outside of Service Fabric. + /// + internal static string LocalCompiledStaticFilesPath => Path.Combine(Environment.CurrentDirectory, "..", "maestro-angular", "dist", "maestro-angular"); + private static void Main() { - ServiceHostWebSite.Run("Maestro.WebType"); + var options = new ServiceHostWebSiteOptions(); + + // Run local Maestro.Web (when outside of SF) on HTTPS too + if (!ServiceFabricHelpers.RunningInServiceFabric() && Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") + { + options.Urls = options.Urls.Append($"https://localhost:{LocalHttpsPort}").ToList(); + } + + ServiceHostWebSite.Run("Maestro.WebType", options); } } diff --git a/src/Maestro/Maestro.Web/Properties/launchSettings.json b/src/Maestro/Maestro.Web/Properties/launchSettings.json index 8af5d6183d..ffe446495f 100644 --- a/src/Maestro/Maestro.Web/Properties/launchSettings.json +++ b/src/Maestro/Maestro.Web/Properties/launchSettings.json @@ -1,30 +1,14 @@ { "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "api/values", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "Maestro.Web": { "commandName": "Project", - "commandLineArgs": "get-build", "launchBrowser": true, - "launchUrl": "api/values", + "launchUrl": "https://localhost:44321/", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_HTTPS_PORT": "44321" }, - "applicationUrl": "http://localhost:59621/" - } - }, - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:59620/", - "sslPort": 0 + "applicationUrl": "http://localhost:44321/" } } } \ No newline at end of file diff --git a/src/Maestro/Maestro.Web/Startup.cs b/src/Maestro/Maestro.Web/Startup.cs index 2e4f2ce2a8..d8c5fba6d4 100644 --- a/src/Maestro/Maestro.Web/Startup.cs +++ b/src/Maestro/Maestro.Web/Startup.cs @@ -4,24 +4,29 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Text; +using System.Threading; using System.Threading.Tasks; +using Azure.Core; using Azure.Identity; using EntityFrameworkCore.Triggers; using FluentValidation.AspNetCore; using Maestro.Authentication; +using Maestro.Common.AzureDevOpsTokens; using Maestro.Contracts; -using Maestro.Data.Models; using Maestro.Data; +using Maestro.Data.Models; using Maestro.DataProviders; using Maestro.MergePolicies; using Microsoft.AspNetCore.ApiPagination; using Microsoft.AspNetCore.ApiVersioning; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; @@ -30,25 +35,25 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Rewrite; using Microsoft.DotNet.DarcLib; +using Microsoft.DotNet.DarcLib.Helpers; using Microsoft.DotNet.GitHub.Authentication; using Microsoft.DotNet.Kusto; +using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.ServiceFabric.ServiceHost; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; -using Newtonsoft.Json; using Swashbuckle.AspNetCore.Swagger; -using Maestro.Common.AzureDevOpsTokens; -using Microsoft.DotNet.DarcLib.Helpers; namespace Maestro.Web; @@ -111,7 +116,7 @@ public Task ProcessAsync(JToken argumentToken) public static JToken CreateArgs(BuildChannel channel) { - return JToken.FromObject(new Arguments {BuildId = channel.BuildId, ChannelId = channel.ChannelId}); + return JToken.FromObject(new Arguments { BuildId = channel.BuildId, ChannelId = channel.ChannelId }); } private struct Arguments @@ -197,12 +202,13 @@ public override void ConfigureServices(IServiceCollection services) options.Cookie.IsEssential = true; }); - services.AddControllers() + services + .AddControllers() .AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); options.SerializerSettings.Converters.Add(new StringEnumConverter - {NamingStrategy = new CamelCaseNamingStrategy()}); + { NamingStrategy = new CamelCaseNamingStrategy() }); options.SerializerSettings.Converters.Add( new IsoDateTimeConverter { @@ -213,11 +219,15 @@ public override void ConfigureServices(IServiceCollection services) services.AddSingleton(Configuration); - services.ConfigureAuthServices( - !HostingEnvironment.IsDevelopment(), - Configuration.GetSection("GitHubAuthentication"), - "/api", - Configuration.GetSection("EntraAuthentication")); + if (IsLocalKestrelDevMode) + { + services.AddHttpsRedirection(options => + { + options.HttpsPort = Program.LocalHttpsPort; + }); + } + + services.ConfigureAuthServices("/api", Configuration.GetSection("EntraAuthentication")); services.AddSingleton(); services.AddSingleton(provider => provider.GetRequiredService()); @@ -233,6 +243,7 @@ public override void ConfigureServices(IServiceCollection services) .GetCustomAttribute() ?.InformationalVersion); }); + services.AddSingleton(); services.Configure(Configuration.GetSection("GitHub")); services.Configure(Configuration.GetSection("AzureDevOps")); @@ -240,6 +251,21 @@ public override void ConfigureServices(IServiceCollection services) services.AddKustoClientProvider("Kusto"); + if (ApiRedirectionEnabled) + { + var targetUri = ApiRedirectionTarget; + var apiRedirectSection = Configuration.GetSection("ApiRedirect"); + var token = apiRedirectSection["Token"]; + var managedIdentityId = apiRedirectSection["ManagedIdentityClientId"]; + + services.AddKeyedSingleton(targetUri, MaestroApiFactory.GetAuthenticated( + targetUri, + accessToken: token, + managedIdentityId: managedIdentityId, + federatedToken: null, + disableInteractiveAuth: !IsLocalKestrelDevMode)); + } + // We do not use AddMemoryCache here. We use our own cache because we wish to // use a sized cache and some components, such as EFCore, do not implement their caching // in such a way that will work with sizing. @@ -293,15 +319,21 @@ private void ConfigureApiExceptions(IApplicationBuilder app) }); } - private bool DoApiRedirect => !string.IsNullOrEmpty(ApiRedirectTarget); - private string ApiRedirectTarget => Configuration.GetSection("ApiRedirect")["uri"]; - private string ApiRedirectToken => Configuration.GetSection("ApiRedirect")["token"]; + // When we run outside of Service Fabric locally + private bool IsLocalKestrelDevMode => HostingEnvironment.IsDevelopment() && !ServiceFabricHelpers.RunningInServiceFabric(); + private bool ApiRedirectionEnabled => !string.IsNullOrEmpty(ApiRedirectionTarget); + private string ApiRedirectionTarget => Configuration.GetSection("ApiRedirect")["Uri"]; private async Task ApiRedirectHandler(HttpContext ctx) { var logger = ctx.RequestServices.GetRequiredService>(); - logger.LogInformation("Preparing for redirect: enabled: '{redirectEnabled}', uri: '{redirectUrl}'", DoApiRedirect, ApiRedirectTarget); - if (ctx.User == null) + logger.LogDebug("Preparing for redirect: enabled: '{redirectEnabled}', uri: '{redirectUrl}'", ApiRedirectionEnabled, ApiRedirectionTarget); + + var authTasks = AuthenticationConfiguration.AuthenticationSchemes.Select(ctx.AuthenticateAsync); + var authResults = await Task.WhenAll(authTasks); + var success = authResults.FirstOrDefault(t => t.Succeeded); + + if (ctx.User == null || success == null) { logger.LogInformation("Rejecting redirect because of missing authentication"); ctx.Response.StatusCode = (int)HttpStatusCode.Forbidden; @@ -309,7 +341,7 @@ private async Task ApiRedirectHandler(HttpContext ctx) } var authService = ctx.RequestServices.GetRequiredService(); - AuthorizationResult result = await authService.AuthorizeAsync(ctx.User, AuthenticationConfiguration.MsftAuthorizationPolicyName); + AuthorizationResult result = await authService.AuthorizeAsync(success.Ticket.Principal, AuthenticationConfiguration.MsftAuthorizationPolicyName); if (!result.Succeeded) { logger.LogInformation("Rejecting redirect because authorization failed"); @@ -317,11 +349,10 @@ private async Task ApiRedirectHandler(HttpContext ctx) return; } - using (var client = new HttpClient(new HttpClientHandler { CheckCertificateRevocationList = true })) { logger.LogInformation("Preparing proxy request to {proxyPath}", ctx.Request.Path); - var uri = new UriBuilder(ApiRedirectTarget) + var uri = new UriBuilder(ApiRedirectionTarget) { Path = ctx.Request.Path, Query = ctx.Request.QueryString.ToUriComponent(), @@ -330,9 +361,11 @@ private async Task ApiRedirectHandler(HttpContext ctx) string absoluteUri = uri.Uri.AbsoluteUri; logger.LogInformation("Service proxied request to {url}", absoluteUri); await ctx.ProxyRequestAsync(client, absoluteUri, - req => + async req => { - req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ApiRedirectToken); + var maestroApi = ctx.RequestServices.GetRequiredKeyedService(ApiRedirectionTarget); + AccessToken token = await maestroApi.Options.Credentials.GetTokenAsync(new(), CancellationToken.None); + req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token); }); } } @@ -344,19 +377,13 @@ private void ConfigureApi(IApplicationBuilder app) var logger = app.ApplicationServices.GetRequiredService>(); logger.LogInformation( - "Configuring API, env: '{env}', isDev: {isDev}, inSF: {inSF}, forceLocal: '{forceLocal}'", + "Configuring API, env: '{env}', isDev: {isDev}, inSF: {inSF}, redirection: {redirection}", HostingEnvironment.EnvironmentName, HostingEnvironment.IsDevelopment(), ServiceFabricHelpers.RunningInServiceFabric(), - Configuration["ForceLocalApi"] - ); + ApiRedirectionEnabled); - if (HostingEnvironment.IsDevelopment() && - !ServiceFabricHelpers.RunningInServiceFabric() && - !string.Equals( - Configuration["ForceLocalApi"], - true.ToString(), - StringComparison.OrdinalIgnoreCase)) + if (IsLocalKestrelDevMode && ApiRedirectionEnabled) { // Redirect api requests to prod when running locally outside of service fabric // This is for the `ng serve` local debugging case for the website @@ -366,7 +393,6 @@ private void ConfigureApi(IApplicationBuilder app) !ctx.Request.Path.Value.EndsWith("swagger.json"), a => { - app.UseAuthentication(); a.Run(ApiRedirectHandler); }); } @@ -384,7 +410,7 @@ private void ConfigureApi(IApplicationBuilder app) return next(); }); app.UseSwagger(); - + app.UseAuthentication(); app.UseRouting(); app.UseAuthorization(); @@ -408,7 +434,7 @@ private void ConfigureCookieAuthedApi(IApplicationBuilder app) { app.UseExceptionHandler(ConfigureApiExceptions); - if (DoApiRedirect) + if (ApiRedirectionEnabled) { app.MapWhen(ctx => !ctx.Request.Cookies.TryGetValue("Skip-Api-Redirect", out _), a => @@ -420,7 +446,7 @@ private void ConfigureCookieAuthedApi(IApplicationBuilder app) a.Run(ApiRedirectHandler); }); } - + app.UseAuthentication(); app.UseRewriter(new RewriteOptions().AddRewrite("^_/(.*)", "$1", true)); app.UseRouting(); @@ -448,6 +474,19 @@ public override void Configure(IApplicationBuilder app) if (HostingEnvironment.IsDevelopment()) { app.UseDeveloperExceptionPage(); + + if (!ServiceFabricHelpers.RunningInServiceFabric()) + { + app.UseHttpsRedirection(); + + // When running Maestro.Web locally (not through SF), we need to add compiled static files + app.UseStaticFiles(new StaticFileOptions() + { + FileProvider = new CompositeFileProvider( + new PhysicalFileProvider(Program.LocalCompiledStaticFilesPath), + new PhysicalFileProvider(Path.Combine(Environment.CurrentDirectory, "wwwroot"))), + }); + } } else { @@ -499,7 +538,7 @@ public override void Configure(IApplicationBuilder app) return next(); }); - if (HostingEnvironment.IsDevelopment() && !ServiceFabricHelpers.RunningInServiceFabric()) + if (IsLocalKestrelDevMode) { // In local dev with the `ng serve` scenario, just redirect /_/api to /api app.UseRewriter(new RewriteOptions().AddRewrite("^_/(.*)", "$1", true)); @@ -517,7 +556,7 @@ public override void Configure(IApplicationBuilder app) app.UseStatusCodePagesWithReExecute("/Error", "?code={0}"); app.UseCookiePolicy(); app.UseStaticFiles(); - + app.UseAuthentication(); app.UseRouting(); app.UseAuthorization(); @@ -545,6 +584,6 @@ private static void AngularIndexHtmlRedirect(IApplicationBuilder app) app.UseAuthentication(); app.UseRouting(); app.UseAuthorization(); - app.UseEndpoints(e => { e.MapRazorPages(); }); + app.UseEndpoints(e => e.MapRazorPages()); } } diff --git a/src/Maestro/SubscriptionActorService/.config/settings.Development.json b/src/Maestro/SubscriptionActorService/.config/settings.Development.json index d43389d25a..00cc317367 100644 --- a/src/Maestro/SubscriptionActorService/.config/settings.Development.json +++ b/src/Maestro/SubscriptionActorService/.config/settings.Development.json @@ -17,14 +17,8 @@ "UseAzCliAuthentication": true }, "AzureDevOps": { - "dnceng": { - "Token": "[vault(dn-bot-dnceng-build-rw-code-rw-release-rw)]" - }, - "devdiv": { - "Token": "[vault(dn-bot-devdiv-build-rw-code-rw-release-rw)]" - }, - "domoreexp": { - "Token": "[vault(dn-bot-domoreexp-build-rw-code-rw-release-rw)]" + "default": { + "UseLocalCredentials": true } } -} \ No newline at end of file +} diff --git a/src/Maestro/maestro-angular/src/app/app.component.html b/src/Maestro/maestro-angular/src/app/app.component.html index 277b2c6c07..339c0ea0c4 100644 --- a/src/Maestro/maestro-angular/src/app/app.component.html +++ b/src/Maestro/maestro-angular/src/app/app.component.html @@ -26,7 +26,6 @@ {{userName}}
- Tokens {{isApiRedirectSkipped ? 'Enable' : 'Disable'}} api redirection @@ -53,15 +52,20 @@
-

You are not authorized to use this site. If you have not done so yet, please submit a request to join the arcade-contrib group on GitHub. After approval, it will take about 15 minutes for those permissions to propagate.

-

If you have joined the group and are still facing problems, please contact dnceng.

+

+ You are not authorized to use this site. If you have not done so yet, please submit a request to join the + dotnetes-maestro-users group. + After approval, it will take about 15 minutes for those permissions to propagate.

+

+ If you have joined the group and are still facing problems, please contact .NET Product Construction Services. +

Please Sign In to use this - site.

+ site. Make sure you have joined the dotnetes-maestro-users group.

diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/AddBuildToChannelOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/AddBuildToChannelOperation.cs index 06d9081768..086b3c25bb 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/AddBuildToChannelOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/AddBuildToChannelOperation.cs @@ -14,7 +14,6 @@ using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; using Microsoft.DotNet.Services.Utility; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -51,11 +50,20 @@ internal class AddBuildToChannelOperation : Operation }; private readonly AddBuildToChannelCommandLineOptions _options; - - public AddBuildToChannelOperation(AddBuildToChannelCommandLineOptions options) - : base(options) + private readonly ILogger _logger; + private readonly IAzureDevOpsClient _azdoClient; + private readonly IBarApiClient _barClient; + + public AddBuildToChannelOperation( + AddBuildToChannelCommandLineOptions options, + IBarApiClient barClient, + IAzureDevOpsClient azdoClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; + _azdoClient = azdoClient; } /// @@ -66,9 +74,7 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - - Build build = await barClient.GetBuildAsync(_options.Id); + Build build = await _barClient.GetBuildAsync(_options.Id); if (build == null) { Console.WriteLine($"Could not find a build with id '{_options.Id}'."); @@ -97,7 +103,7 @@ public override async Task ExecuteAsync() if (!string.IsNullOrEmpty(_options.Channel)) { - Channel targetChannel = await UxHelpers.ResolveSingleChannel(barClient, _options.Channel); + Channel targetChannel = await UxHelpers.ResolveSingleChannel(_barClient, _options.Channel); if (targetChannel == null) { return Constants.ErrorCode; @@ -108,7 +114,7 @@ public override async Task ExecuteAsync() if (_options.AddToDefaultChannels) { - IEnumerable defaultChannels = await barClient.GetDefaultChannelsAsync( + IEnumerable defaultChannels = await _barClient.GetDefaultChannelsAsync( build.GetRepository(), build.GetBranch()); @@ -153,7 +159,7 @@ public override async Task ExecuteAsync() // Queues a build of the Build Promotion pipeline that will takes care of making sure // that the build assets are published to the right location and also promoting the build // to the requested channel - int promoteBuildQueuedStatus = await PromoteBuildAsync(build, targetChannels, barClient) + int promoteBuildQueuedStatus = await PromoteBuildAsync(build, targetChannels, _barClient) .ConfigureAwait(false); if (promoteBuildQueuedStatus != Constants.SuccessCode) @@ -162,7 +168,7 @@ public override async Task ExecuteAsync() } // Get the latest build information to verify the channels - build = await barClient.GetBuildAsync(build.Id); + build = await _barClient.GetBuildAsync(build.Id); Console.WriteLine($"Assigning build '{build.Id}' to the following channel(s):"); foreach (var channel in targetChannels) @@ -178,7 +184,7 @@ public override async Task ExecuteAsync() foreach (var targetChannel in targetChannels) { - IEnumerable appSubscriptions = await barClient.GetSubscriptionsAsync( + IEnumerable appSubscriptions = await _barClient.GetSubscriptionsAsync( sourceRepo: buildRepo, channelId: targetChannel.Id); @@ -196,7 +202,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, $"Error: Failed to assign build '{_options.Id}' to channel '{_options.Channel}'."); + _logger.LogError(e, $"Error: Failed to assign build '{_options.Id}' to channel '{_options.Channel}'."); return Constants.ErrorCode; } } @@ -223,9 +229,7 @@ private async Task PromoteBuildAsync(Build build, List targetChann return Constants.ErrorCode; } - var azdoClient = Provider.GetRequiredService(); - - var targetAzdoBuildStatus = await ValidateAzDOBuildAsync(azdoClient, build.AzureDevOpsAccount, build.AzureDevOpsProject, build.AzureDevOpsBuildId.Value) + var targetAzdoBuildStatus = await ValidateAzDOBuildAsync(_azdoClient, build.AzureDevOpsAccount, build.AzureDevOpsProject, build.AzureDevOpsBuildId.Value) .ConfigureAwait(false); if (!targetAzdoBuildStatus) @@ -266,7 +270,7 @@ private async Task PromoteBuildAsync(Build build, List targetChann // Pass the same values to the variables and pipeline parameters so this works with the // v2 and v3 versions of the promotion pipeline. - int azdoBuildId = await azdoClient.StartNewBuildAsync(build.AzureDevOpsAccount, + int azdoBuildId = await _azdoClient.StartNewBuildAsync(build.AzureDevOpsAccount, promotionPipelineInformation.project, promotionPipelineInformation.pipelineId, arcadeSDKSourceBranch, @@ -294,7 +298,7 @@ private async Task PromoteBuildAsync(Build build, List targetChann { Console.WriteLine($"Waiting '{waitIntervalInSeconds.TotalSeconds}' seconds for promotion build to complete."); await Task.Delay(waitIntervalInSeconds); - promotionBuild = await azdoClient.GetBuildAsync( + promotionBuild = await _azdoClient.GetBuildAsync( build.AzureDevOpsAccount, promotionPipelineInformation.project, azdoBuildId); @@ -403,8 +407,7 @@ private async Task ValidateAzDOBuildAsync(IAzureDevOpsClient azdoClient, s build.AzureDevOpsRepository : build.GitHubRepository; - IRemote repoRemote = RemoteFactory.GetRemote(_options, sourceBuildRepo, Logger); - IBarApiClient barClient = Provider.GetRequiredService(); + IRemote repoRemote = RemoteFactory.GetRemote(_options, sourceBuildRepo, _logger); IEnumerable sourceBuildDependencies = await repoRemote.GetDependenciesAsync(sourceBuildRepo, build.Commit) .ConfigureAwait(false); @@ -417,7 +420,7 @@ private async Task ValidateAzDOBuildAsync(IAzureDevOpsClient azdoClient, s return (null, null); } - IEnumerable listArcadeSDKAssets = await barClient.GetAssetsAsync(sourceBuildArcadeSDKDependency.Name, sourceBuildArcadeSDKDependency.Version) + IEnumerable listArcadeSDKAssets = await _barClient.GetAssetsAsync(sourceBuildArcadeSDKDependency.Name, sourceBuildArcadeSDKDependency.Version) .ConfigureAwait(false); Asset sourceBuildArcadeSDKDepAsset = listArcadeSDKAssets.FirstOrDefault(); @@ -428,7 +431,7 @@ private async Task ValidateAzDOBuildAsync(IAzureDevOpsClient azdoClient, s return (null, null); } - Build sourceBuildArcadeSDKDepBuild = await barClient.GetBuildAsync(sourceBuildArcadeSDKDepAsset.BuildId); + Build sourceBuildArcadeSDKDepBuild = await _barClient.GetBuildAsync(sourceBuildArcadeSDKDepAsset.BuildId); if (sourceBuildArcadeSDKDepBuild == null) { diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/AddChannelOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/AddChannelOperation.cs index 03879899be..f781087191 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/AddChannelOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/AddChannelOperation.cs @@ -8,7 +8,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -17,10 +16,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class AddChannelOperation : Operation { private readonly AddChannelCommandLineOptions _options; - public AddChannelOperation(AddChannelCommandLineOptions options) - : base(options) + private readonly ILogger _logger; + private readonly IBarApiClient _barClient; + + public AddChannelOperation( + AddChannelCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -31,17 +37,15 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - // If the user tried to mark as internal, indicate that this is currently // unsupported. if (_options.Internal) { - Logger.LogError("Cannot currently mark channels as internal."); + _logger.LogError("Cannot currently mark channels as internal."); return Constants.ErrorCode; } - Channel newChannelInfo = await barClient.CreateChannelAsync(_options.Name, _options.Classification); + Channel newChannelInfo = await _barClient.CreateChannelAsync(_options.Name, _options.Classification); switch (_options.OutputFormat) { case DarcOutputType.json: @@ -70,20 +74,13 @@ public override async Task ExecuteAsync() } catch (RestApiException e) when (e.Response.Status == (int) HttpStatusCode.Conflict) { - Logger.LogError($"An existing channel with name '{_options.Name}' already exists"); + _logger.LogError($"An existing channel with name '{_options.Name}' already exists"); return Constants.ErrorCode; } catch (Exception e) { - Logger.LogError(e, "Error: Failed to create new channel."); + _logger.LogError(e, "Error: Failed to create new channel."); return Constants.ErrorCode; } } - - protected override bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch - { - DarcOutputType.json => true, - _ => base.IsOutputFormatSupported(outputFormat), - }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/AddDefaultChannelOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/AddDefaultChannelOperation.cs index 610746dcad..3a584fba61 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/AddDefaultChannelOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/AddDefaultChannelOperation.cs @@ -8,7 +8,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Services.Utility; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -16,19 +15,24 @@ namespace Microsoft.DotNet.Darc.Operations; internal class AddDefaultChannelOperation : Operation { private readonly AddDefaultChannelCommandLineOptions _options; + private readonly ILogger _logger; + private readonly IBarApiClient _barClient; - public AddDefaultChannelOperation(AddDefaultChannelCommandLineOptions options) - : base(options) + public AddDefaultChannelOperation( + AddDefaultChannelCommandLineOptions options, + ILogger logger, + IBarApiClient barClient) { _options = options; + _logger = logger; + _barClient = barClient; } public override async Task ExecuteAsync() { try { - IRemote repoRemote = RemoteFactory.GetRemote(_options, _options.Repository, Logger); - IBarApiClient barClient = Provider.GetRequiredService(); + IRemote repoRemote = RemoteFactory.GetRemote(_options, _options.Repository, _logger); // Users can ignore the flag and pass in -regex: but to prevent typos we'll avoid that. _options.Branch = _options.UseBranchAsRegex ? $"-regex:{_options.Branch}" : GitHelpers.NormalizeBranchName(_options.Branch); @@ -39,7 +43,7 @@ public override async Task ExecuteAsync() return Constants.ErrorCode; } - await barClient.AddDefaultChannelAsync(_options.Repository, _options.Branch, _options.Channel); + await _barClient.AddDefaultChannelAsync(_options.Repository, _options.Branch, _options.Channel); return Constants.SuccessCode; } @@ -50,7 +54,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to add a new default channel association."); + _logger.LogError(e, "Error: Failed to add a new default channel association."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/AddDependencyOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/AddDependencyOperation.cs index 4802fddeaf..3835efa756 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/AddDependencyOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/AddDependencyOperation.cs @@ -1,32 +1,35 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.DotNet.Darc.Options; -using Microsoft.DotNet.DarcLib; -using Microsoft.Extensions.Logging; using System; using System.IO; using System.Threading.Tasks; +using Microsoft.DotNet.Darc.Options; +using Microsoft.DotNet.DarcLib; +using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; internal class AddDependencyOperation : Operation { private readonly AddDependencyCommandLineOptions _options; + private readonly ILogger _logger; - public AddDependencyOperation(AddDependencyCommandLineOptions options) - : base(options) + public AddDependencyOperation( + AddDependencyCommandLineOptions options, + ILogger logger) { _options = options; + _logger = logger; } public override async Task ExecuteAsync() { DependencyType type = _options.Type.ToLower() == "toolset" ? DependencyType.Toolset : DependencyType.Product; - var local = new Local(_options.GetRemoteTokenProvider(), Logger); + var local = new Local(_options.GetRemoteTokenProvider(), _logger); - DependencyDetail dependency = new DependencyDetail + var dependency = new DependencyDetail { Name = _options.Name, Version = _options.Version ?? string.Empty, @@ -44,13 +47,13 @@ public override async Task ExecuteAsync() } catch (FileNotFoundException exc) { - Logger.LogError(exc, $"One of the version files is missing. Please make sure to add all files " + + _logger.LogError(exc, $"One of the version files is missing. Please make sure to add all files " + "included in https://github.com/dotnet/arcade/blob/main/Documentation/DependencyDescriptionFormat.md#dependency-description-details"); return Constants.ErrorCode; } catch (Exception exc) { - Logger.LogError(exc, $"Failed to add dependency '{dependency.Name}' to repository."); + _logger.LogError(exc, $"Failed to add dependency '{dependency.Name}' to repository."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/AddSubscriptionOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/AddSubscriptionOperation.cs index 933305df19..9abaa8a187 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/AddSubscriptionOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/AddSubscriptionOperation.cs @@ -14,7 +14,6 @@ using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; using Microsoft.DotNet.Services.Utility; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; @@ -23,11 +22,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class AddSubscriptionOperation : Operation { private readonly AddSubscriptionCommandLineOptions _options; + private readonly ILogger _logger; + private readonly IBarApiClient _barClient; - public AddSubscriptionOperation(AddSubscriptionCommandLineOptions options) - : base(options) + public AddSubscriptionOperation( + AddSubscriptionCommandLineOptions options, + ILogger logger, + IBarApiClient barClient) { _options = options; + _logger = logger; + _barClient = barClient; } /// @@ -35,8 +40,6 @@ public AddSubscriptionOperation(AddSubscriptionCommandLineOptions options) /// public override async Task ExecuteAsync() { - IBarApiClient barClient = Provider.GetRequiredService(); - if (_options.IgnoreChecks.Any() && !_options.AllChecksSuccessfulMergePolicy) { Console.WriteLine($"--ignore-checks must be combined with --all-checks-passed"); @@ -123,7 +126,7 @@ public override async Task ExecuteAsync() if (!string.IsNullOrEmpty(sourceDirectory) && !string.IsNullOrEmpty(targetDirectory)) { - Logger.LogError("Only one of source or target directory can be specified for source-enabled subscriptions."); + _logger.LogError("Only one of source or target directory can be specified for source-enabled subscriptions."); return Constants.ErrorCode; } @@ -138,13 +141,13 @@ public override async Task ExecuteAsync() string.IsNullOrEmpty(updateFrequency) || !Constants.AvailableFrequencies.Contains(updateFrequency, StringComparer.OrdinalIgnoreCase)) { - Logger.LogError($"Missing input parameters for the subscription. Please see command help or remove --quiet/-q for interactive mode"); + _logger.LogError($"Missing input parameters for the subscription. Please see command help or remove --quiet/-q for interactive mode"); return Constants.ErrorCode; } if (sourceEnabled && string.IsNullOrEmpty(sourceDirectory) && string.IsNullOrEmpty(targetDirectory)) { - Logger.LogError("One of source or target directory is required for source-enabled subscriptions."); + _logger.LogError("One of source or target directory is required for source-enabled subscriptions."); return Constants.ErrorCode; } } @@ -152,19 +155,19 @@ public override async Task ExecuteAsync() { if (!string.IsNullOrEmpty(failureNotificationTags) && batchable) { - Logger.LogWarning("Failure Notification Tags may be set, but will not be used while in batched mode."); + _logger.LogWarning("Failure Notification Tags may be set, but will not be used while in batched mode."); } // Grab existing subscriptions to get suggested values. // TODO: When this becomes paged, set a max number of results to avoid // pulling too much. - var suggestedRepos = barClient.GetSubscriptionsAsync(); - var suggestedChannels = barClient.GetChannelsAsync(); + var suggestedRepos = _barClient.GetSubscriptionsAsync(); + var suggestedChannels = _barClient.GetChannelsAsync(); // Help the user along with a form. We'll use the API to gather suggested values // from existing subscriptions based on the input parameters. var addSubscriptionPopup = new AddSubscriptionPopUp("add-subscription/add-subscription-todo", - Logger, + _logger, channel, sourceRepository, targetRepository, @@ -182,7 +185,7 @@ public override async Task ExecuteAsync() targetDirectory, excludedAssets); - var uxManager = new UxManager(_options.GitLocation, Logger); + var uxManager = new UxManager(_options.GitLocation, _logger); int exitCode = _options.ReadStandardIn ? uxManager.ReadFromStdIn(addSubscriptionPopup) : uxManager.PopUp(addSubscriptionPopup); @@ -218,7 +221,7 @@ public override async Task ExecuteAsync() // target repo/branch, warn the user. if (batchable) { - var existingMergePolicies = await barClient.GetRepositoryMergePoliciesAsync(targetRepository, targetBranch); + var existingMergePolicies = await _barClient.GetRepositoryMergePoliciesAsync(targetRepository, targetBranch); if (!existingMergePolicies.Any()) { Console.WriteLine("Warning: Batchable subscription doesn't have any repository merge policies. " + @@ -234,7 +237,7 @@ public override async Task ExecuteAsync() } // Verify the target - IRemote targetVerifyRemote = RemoteFactory.GetRemote(_options, targetRepository, Logger); + IRemote targetVerifyRemote = RemoteFactory.GetRemote(_options, targetRepository, _logger); if (!(await UxHelpers.VerifyAndConfirmBranchExistsAsync(targetVerifyRemote, targetRepository, targetBranch, !_options.Quiet))) { Console.WriteLine("Aborting subscription creation."); @@ -242,14 +245,14 @@ public override async Task ExecuteAsync() } // Verify the source. - IRemote sourceVerifyRemote = RemoteFactory.GetRemote(_options, sourceRepository, Logger); + IRemote sourceVerifyRemote = RemoteFactory.GetRemote(_options, sourceRepository, _logger); if (!await UxHelpers.VerifyAndConfirmRepositoryExistsAsync(sourceVerifyRemote, sourceRepository, !_options.Quiet)) { Console.WriteLine("Aborting subscription creation."); return Constants.ErrorCode; } - Subscription newSubscription = await barClient.CreateSubscriptionAsync( + Subscription newSubscription = await _barClient.CreateSubscriptionAsync( channel, sourceRepository, targetRepository, @@ -271,7 +274,7 @@ public override async Task ExecuteAsync() bool triggerAutomatically = _options.TriggerOnCreate || UxHelpers.PromptForYesNo("Trigger this subscription immediately?"); if (triggerAutomatically) { - await barClient.TriggerSubscriptionAsync(newSubscription.Id); + await _barClient.TriggerSubscriptionAsync(newSubscription.Id); Console.WriteLine($"Subscription '{newSubscription.Id}' triggered."); } } @@ -286,12 +289,12 @@ public override async Task ExecuteAsync() catch (RestApiException e) when (e.Response.Status == (int) System.Net.HttpStatusCode.BadRequest) { // Could have been some kind of validation error (e.g. channel doesn't exist) - Logger.LogError($"Failed to create subscription: {e.Response.Content}"); + _logger.LogError($"Failed to create subscription: {e.Response.Content}"); return Constants.ErrorCode; } catch (Exception e) { - Logger.LogError(e, $"Failed to create subscription."); + _logger.LogError(e, $"Failed to create subscription."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/AuthenticateOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/AuthenticateOperation.cs index dbe2dc6849..7414929068 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/AuthenticateOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/AuthenticateOperation.cs @@ -15,10 +15,14 @@ namespace Microsoft.DotNet.Darc.Operations; internal class AuthenticateOperation : Operation { private readonly AuthenticateCommandLineOptions _options; - public AuthenticateOperation(AuthenticateCommandLineOptions options) - : base(options) + private readonly ILogger _logger; + + public AuthenticateOperation( + AuthenticateCommandLineOptions options, + ILogger logger) { _options = options; + _logger = logger; } /// @@ -39,20 +43,20 @@ public override Task ExecuteAsync() } catch (Exception ex) { - Logger.LogWarning("Failed to clear authentication cache: {message}", ex.Message); + _logger.LogWarning("Failed to clear authentication cache: {message}", ex.Message); } } var defaultSettings = new LocalSettings(); - defaultSettings.SaveSettingsFile(Logger); + defaultSettings.SaveSettingsFile(_logger); return Task.FromResult(Constants.SuccessCode); } else { - var initEditorPopUp = new AuthenticateEditorPopUp("authenticate-settings/darc-authenticate", Logger); + var initEditorPopUp = new AuthenticateEditorPopUp("authenticate-settings/darc-authenticate", _logger); - var uxManager = new UxManager(_options.GitLocation, Logger); + var uxManager = new UxManager(_options.GitLocation, _logger); return Task.FromResult(uxManager.PopUp(initEditorPopUp)); } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/CloneOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/CloneOperation.cs index 763df81d8e..d417c11bd7 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/CloneOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/CloneOperation.cs @@ -3,7 +3,6 @@ using Microsoft.DotNet.Darc.Options; using Microsoft.DotNet.DarcLib; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -59,12 +58,19 @@ namespace Microsoft.DotNet.Darc.Operations; internal class CloneOperation : Operation { private readonly CloneCommandLineOptions _options; + private readonly IRemoteFactory _remoteFactory; + private readonly ILogger _logger; private const string GitDirRedirectPrefix = "gitdir: "; - public CloneOperation(CloneCommandLineOptions options) : base(options) + public CloneOperation( + CloneCommandLineOptions options, + IRemoteFactory remoteFactory, + ILogger logger) { _options = options; + _remoteFactory = remoteFactory; + _logger = logger; } public override async Task ExecuteAsync() @@ -75,32 +81,31 @@ public override async Task ExecuteAsync() // use a set to accumulate dependencies as we go HashSet accumulatedDependencies = []; // at the end of each depth level, these are added to the queue to clone - IRemoteFactory remoteFactory = Provider.GetRequiredService(); if (string.IsNullOrWhiteSpace(_options.RepoUri)) { - var local = new Local(_options.GetRemoteTokenProvider(), Logger); + var local = new Local(_options.GetRemoteTokenProvider(), _logger); IEnumerable rootDependencies = await local.GetDependenciesAsync(); IEnumerable stripped = rootDependencies.Select(StrippedDependency.GetDependency); foreach (StrippedDependency d in stripped) { if (_options.IgnoredRepos.Any(r => r.Equals(d.RepoUri, StringComparison.OrdinalIgnoreCase))) { - Logger.LogDebug($"Skipping ignored repo {d.RepoUri} (at {d.Commit})"); + _logger.LogDebug($"Skipping ignored repo {d.RepoUri} (at {d.Commit})"); } else { accumulatedDependencies.Add(d); } } - Logger.LogInformation($"Found {rootDependencies.Count()} local dependencies. Starting deep clone..."); + _logger.LogInformation($"Found {rootDependencies.Count()} local dependencies. Starting deep clone..."); } else { // Start with the root repo we were asked to clone var rootDep = StrippedDependency.GetDependency(_options.RepoUri, _options.Version); accumulatedDependencies.Add(rootDep); - Logger.LogInformation($"Starting deep clone of {rootDep.RepoUri}@{rootDep.Commit}"); + _logger.LogInformation($"Starting deep clone of {rootDep.RepoUri}@{rootDep.Commit}"); } var dependenciesToClone = new Queue(); @@ -127,56 +132,56 @@ public override async Task ExecuteAsync() Local local; // Scenarios we handle: no/existing/orphaned master folder cross no/existing .gitdir - await HandleMasterCopy(remoteFactory, repo.RepoUri, masterGitRepoPath, masterRepoGitDirPath); + await HandleMasterCopy(_remoteFactory, repo.RepoUri, masterGitRepoPath, masterRepoGitDirPath); // if using the default .gitdir path, get that for use in the specific clone. masterRepoGitDirPath ??= GetDefaultMasterGitDirPath(_options.ReposFolder, repo.RepoUri); local = HandleRepoAtSpecificHash(repoPath, repo.Commit, masterRepoGitDirPath); - Logger.LogDebug($"Starting to look for dependencies in {repoPath}"); + _logger.LogDebug($"Starting to look for dependencies in {repoPath}"); try { IEnumerable deps = await local.GetDependenciesAsync(); - IEnumerable filteredDeps = FilterToolsetDependencies(deps, _options.IncludeToolset, Logger); - Logger.LogDebug($"Got {deps.Count()} dependencies and filtered to {filteredDeps.Count()} dependencies"); + IEnumerable filteredDeps = FilterToolsetDependencies(deps, _options.IncludeToolset, _logger); + _logger.LogDebug($"Got {deps.Count()} dependencies and filtered to {filteredDeps.Count()} dependencies"); foreach (DependencyDetail d in filteredDeps) { var dep = StrippedDependency.GetDependency(d); // e.g. arcade depends on previous versions of itself to build, so this would go on forever if (d.RepoUri == repo.RepoUri) { - Logger.LogDebug($"Skipping self-dependency in {repo.RepoUri} ({repo.Commit} => {d.Commit})"); + _logger.LogDebug($"Skipping self-dependency in {repo.RepoUri} ({repo.Commit} => {d.Commit})"); } // circular dependencies that have different hashes, e.g. DotNet-Trusted -> core-setup -> DotNet-Trusted -> ... else if (dep.HasDependencyOn(repo)) { - Logger.LogDebug($"Skipping already-seen circular dependency from {repo.RepoUri} to {d.RepoUri}"); + _logger.LogDebug($"Skipping already-seen circular dependency from {repo.RepoUri} to {d.RepoUri}"); } else if (_options.IgnoredRepos.Any(r => r.Equals(d.RepoUri, StringComparison.OrdinalIgnoreCase))) { - Logger.LogDebug($"Skipping ignored repo {d.RepoUri} (at {d.Commit})"); + _logger.LogDebug($"Skipping ignored repo {d.RepoUri} (at {d.Commit})"); } else if (string.IsNullOrWhiteSpace(d.Commit)) { - Logger.LogWarning($"Skipping dependency from {repo.RepoUri}@{repo.Commit} to {d.RepoUri}: Missing commit."); + _logger.LogWarning($"Skipping dependency from {repo.RepoUri}@{repo.Commit} to {d.RepoUri}: Missing commit."); } else { var stripped = StrippedDependency.GetDependency(d); - Logger.LogDebug($"Adding new dependency {stripped.RepoUri}@{stripped.Commit}"); + _logger.LogDebug($"Adding new dependency {stripped.RepoUri}@{stripped.Commit}"); repo.AddDependency(dep); accumulatedDependencies.Add(stripped); } } - Logger.LogDebug($"done looking for dependencies in {repoPath} at {repo.Commit}"); + _logger.LogDebug($"done looking for dependencies in {repoPath} at {repo.Commit}"); } catch (DirectoryNotFoundException) { - Logger.LogWarning($"Repo {repoPath} appears to have no '/eng' directory at commit {repo.Commit}. Dependency chain is broken here."); + _logger.LogWarning($"Repo {repoPath} appears to have no '/eng' directory at commit {repo.Commit}. Dependency chain is broken here."); } catch (FileNotFoundException) { - Logger.LogWarning($"Repo {repoPath} appears to have no '/eng/Version.Details.xml' file at commit {repo.Commit}. Dependency chain is broken here."); + _logger.LogWarning($"Repo {repoPath} appears to have no '/eng/Version.Details.xml' file at commit {repo.Commit}. Dependency chain is broken here."); } finally { @@ -187,12 +192,12 @@ public override async Task ExecuteAsync() string repoGitRedirectPath = Path.Combine(repoPath, ".git"); if (File.Exists(repoGitRedirectPath)) { - Logger.LogDebug($"Deleting .gitdir redirect {repoGitRedirectPath}"); + _logger.LogDebug($"Deleting .gitdir redirect {repoGitRedirectPath}"); File.Delete(repoGitRedirectPath); } else { - Logger.LogDebug($"No .gitdir redirect found at {repoGitRedirectPath}"); + _logger.LogDebug($"No .gitdir redirect found at {repoGitRedirectPath}"); } } } // end inner while(dependenciesToClone.Any()) @@ -200,18 +205,18 @@ public override async Task ExecuteAsync() if (_options.CloneDepth == 0 && accumulatedDependencies.Any()) { - Logger.LogInformation($"Reached clone depth limit, aborting with {accumulatedDependencies.Count} dependencies remaining"); + _logger.LogInformation($"Reached clone depth limit, aborting with {accumulatedDependencies.Count} dependencies remaining"); foreach (StrippedDependency d in accumulatedDependencies) { - Logger.LogDebug($"Abandoning dependency {d.RepoUri}@{d.Commit}"); + _logger.LogDebug($"Abandoning dependency {d.RepoUri}@{d.Commit}"); } break; } else { _options.CloneDepth--; - Logger.LogDebug($"Clone depth remaining: {_options.CloneDepth}"); - Logger.LogDebug($"Dependencies remaining: {accumulatedDependencies.Count}"); + _logger.LogDebug($"Clone depth remaining: {_options.CloneDepth}"); + _logger.LogDebug($"Dependencies remaining: {accumulatedDependencies.Count}"); } } // end outer while(accumulatedDependencies.Any()) @@ -219,7 +224,7 @@ public override async Task ExecuteAsync() } catch (Exception exc) { - Logger.LogError(exc, "Something failed while cloning."); + _logger.LogError(exc, "Something failed while cloning."); return Constants.ErrorCode; } } @@ -230,16 +235,16 @@ private Local HandleRepoAtSpecificHash(string repoPath, string commit, string ma if (Directory.Exists(repoPath)) { - Logger.LogDebug($"Repo path {repoPath} already exists, assuming we cloned already and skipping"); - local = new Local(_options.GetRemoteTokenProvider(), Logger, repoPath); + _logger.LogDebug($"Repo path {repoPath} already exists, assuming we cloned already and skipping"); + local = new Local(_options.GetRemoteTokenProvider(), _logger, repoPath); } else { - Logger.LogDebug($"Setting up {repoPath} with .gitdir redirect"); + _logger.LogDebug($"Setting up {repoPath} with .gitdir redirect"); Directory.CreateDirectory(repoPath); File.WriteAllText(Path.Combine(repoPath, ".git"), GetGitDirRedirectString(masterRepoGitDirPath)); - Logger.LogInformation($"Checking out {commit} in {repoPath}"); - local = new Local(_options.GetRemoteTokenProvider(), Logger, repoPath); + _logger.LogInformation($"Checking out {commit} in {repoPath}"); + local = new Local(_options.GetRemoteTokenProvider(), _logger, repoPath); local.Checkout(commit, true); } @@ -256,25 +261,25 @@ private async Task HandleMasterCopy(IRemoteFactory remoteFactory, string repoUrl { await HandleMasterCopyWithDefaultGitDir(remoteFactory, repoUrl, masterGitRepoPath, masterRepoGitDirPath); } - var local = new Local(_options.GetRemoteTokenProvider(), Logger, masterGitRepoPath); + var local = new Local(_options.GetRemoteTokenProvider(), _logger, masterGitRepoPath); await local.AddRemoteIfMissingAsync(masterGitRepoPath, repoUrl); } private async Task HandleMasterCopyWithDefaultGitDir(IRemoteFactory remoteFactory, string repoUrl, string masterGitRepoPath, string masterRepoGitDirPath) { - Logger.LogDebug($"Starting master copy for {repoUrl} in {masterGitRepoPath} with default .gitdir"); + _logger.LogDebug($"Starting master copy for {repoUrl} in {masterGitRepoPath} with default .gitdir"); // The master folder doesn't exist. Just clone and set everything up for the first time. if (!Directory.Exists(masterGitRepoPath)) { - Logger.LogInformation($"Cloning master copy of {repoUrl} into {masterGitRepoPath}"); - IRemote repoRemote = await remoteFactory.GetRemoteAsync(repoUrl, Logger); + _logger.LogInformation($"Cloning master copy of {repoUrl} into {masterGitRepoPath}"); + IRemote repoRemote = await remoteFactory.GetRemoteAsync(repoUrl, _logger); await repoRemote.CloneAsync(repoUrl, null, masterGitRepoPath, checkoutSubmodules: true, masterRepoGitDirPath); } // The master folder already exists. We are probably resuming with a different --git-dir-parent setting, or the .gitdir parent was cleaned. else { - Logger.LogDebug($"Checking for existing .gitdir in {masterGitRepoPath}"); + _logger.LogDebug($"Checking for existing .gitdir in {masterGitRepoPath}"); string masterRepoPossibleGitDirPath = Path.Combine(masterGitRepoPath, ".git"); // This repo is not in good shape for us. It needs to be deleted and recreated. if (!Directory.Exists(masterRepoPossibleGitDirPath)) @@ -287,7 +292,7 @@ private async Task HandleMasterCopyWithDefaultGitDir(IRemoteFactory remoteFactor private async Task HandleMasterCopyWithGitDirPath(IRemoteFactory remoteFactory, string repoUrl, string masterGitRepoPath, string masterRepoGitDirPath) { string gitDirRedirect = GetGitDirRedirectString(masterRepoGitDirPath); - Logger.LogDebug($"Starting master copy for {repoUrl} in {masterGitRepoPath} with .gitdir {masterRepoGitDirPath}"); + _logger.LogDebug($"Starting master copy for {repoUrl} in {masterGitRepoPath} with .gitdir {masterRepoGitDirPath}"); // the .gitdir exists already. We are resuming with the same --git-dir-parent setting. if (Directory.Exists(masterRepoGitDirPath)) @@ -303,13 +308,13 @@ private async Task HandleMasterCopyWithGitDirPath(IRemoteFactory remoteFactory, private async Task HandleMasterCopyAndCreateGitDir(IRemoteFactory remoteFactory, string repoUrl, string masterGitRepoPath, string masterRepoGitDirPath, string gitDirRedirect) { - Logger.LogDebug($"Master .gitdir {masterRepoGitDirPath} does not exist"); + _logger.LogDebug($"Master .gitdir {masterRepoGitDirPath} does not exist"); // The master folder also doesn't exist. Just clone and set everything up for the first time. if (!Directory.Exists(masterGitRepoPath)) { - Logger.LogInformation($"Cloning master copy of {repoUrl} into {masterGitRepoPath} with .gitdir path {masterRepoGitDirPath}"); - IRemote repoRemote = await remoteFactory.GetRemoteAsync(repoUrl, Logger); + _logger.LogInformation($"Cloning master copy of {repoUrl} into {masterGitRepoPath} with .gitdir path {masterRepoGitDirPath}"); + IRemote repoRemote = await remoteFactory.GetRemoteAsync(repoUrl, _logger); await repoRemote.CloneAsync(repoUrl, null, masterGitRepoPath, checkoutSubmodules: true, masterRepoGitDirPath); } // The master folder already exists. We are probably resuming with a different --git-dir-parent setting, or the .gitdir parent was cleaned. @@ -319,12 +324,12 @@ private async Task HandleMasterCopyAndCreateGitDir(IRemoteFactory remoteFactory, // The master folder has a full .gitdir. Relocate it to the .gitdir parent directory and update to redirect to that. if (Directory.Exists(masterRepoPossibleGitDirPath)) { - Logger.LogDebug($".gitdir {masterRepoPossibleGitDirPath} exists in {masterGitRepoPath}"); + _logger.LogDebug($".gitdir {masterRepoPossibleGitDirPath} exists in {masterGitRepoPath}"); // Check if the .gitdir is already where we expect it to be first. if (Path.GetFullPath(masterRepoPossibleGitDirPath) != Path.GetFullPath(masterRepoGitDirPath)) { - Logger.LogDebug($"Moving .gitdir {masterRepoPossibleGitDirPath} to expected location {masterRepoGitDirPath}"); + _logger.LogDebug($"Moving .gitdir {masterRepoPossibleGitDirPath} to expected location {masterRepoGitDirPath}"); Directory.Move(masterRepoPossibleGitDirPath, masterRepoGitDirPath); File.WriteAllText(masterRepoPossibleGitDirPath, gitDirRedirect); } @@ -332,12 +337,12 @@ private async Task HandleMasterCopyAndCreateGitDir(IRemoteFactory remoteFactory, // The master folder has a .gitdir redirect. Relocate its .gitdir to where we expect and update the redirect. else if (File.Exists(masterRepoPossibleGitDirPath)) { - Logger.LogDebug($"Master repo {masterGitRepoPath} has a .gitdir redirect"); + _logger.LogDebug($"Master repo {masterGitRepoPath} has a .gitdir redirect"); string relocatedGitDirPath = File.ReadAllText(masterRepoPossibleGitDirPath).Substring(GitDirRedirectPrefix.Length); if (Path.GetFullPath(relocatedGitDirPath) != Path.GetFullPath(masterRepoGitDirPath)) { - Logger.LogDebug($"Existing .gitdir redirect of {relocatedGitDirPath} does not match expected {masterRepoGitDirPath}, moving .gitdir and updating redirect"); + _logger.LogDebug($"Existing .gitdir redirect of {relocatedGitDirPath} does not match expected {masterRepoGitDirPath}, moving .gitdir and updating redirect"); Directory.Move(relocatedGitDirPath, masterRepoGitDirPath); File.WriteAllText(masterRepoPossibleGitDirPath, gitDirRedirect); } @@ -345,7 +350,7 @@ private async Task HandleMasterCopyAndCreateGitDir(IRemoteFactory remoteFactory, // This repo is orphaned. Since it's supposed to be our master copy, adopt it. else { - Logger.LogDebug($"Master repo {masterGitRepoPath} is orphaned, adding .gitdir redirect"); + _logger.LogDebug($"Master repo {masterGitRepoPath} is orphaned, adding .gitdir redirect"); File.WriteAllText(masterRepoPossibleGitDirPath, gitDirRedirect); } } @@ -353,22 +358,22 @@ private async Task HandleMasterCopyAndCreateGitDir(IRemoteFactory remoteFactory, private void HandleMasterCopyWithExistingGitDir(string masterGitRepoPath, string masterRepoGitDirPath, string gitDirRedirect) { - Logger.LogDebug($"Master .gitdir {masterRepoGitDirPath} exists"); + _logger.LogDebug($"Master .gitdir {masterRepoGitDirPath} exists"); // the master folder doesn't exist yet. Create it. if (!Directory.Exists(masterGitRepoPath)) { - Logger.LogDebug($"Master .gitdir exists and master folder {masterGitRepoPath} does not. Creating master folder."); + _logger.LogDebug($"Master .gitdir exists and master folder {masterGitRepoPath} does not. Creating master folder."); Directory.CreateDirectory(masterGitRepoPath); File.WriteAllText(Path.Combine(masterGitRepoPath, ".git"), gitDirRedirect); - var masterLocal = new Local(_options.GetRemoteTokenProvider(), Logger, masterGitRepoPath); - Logger.LogDebug($"Checking out default commit in {masterGitRepoPath}"); + var masterLocal = new Local(_options.GetRemoteTokenProvider(), _logger, masterGitRepoPath); + _logger.LogDebug($"Checking out default commit in {masterGitRepoPath}"); masterLocal.Checkout(null, true); } // The master folder already exists. Redirect it to the .gitdir we expect. else { - Logger.LogDebug($"Master .gitdir exists and master folder {masterGitRepoPath} also exists. Redirecting master folder."); + _logger.LogDebug($"Master .gitdir exists and master folder {masterGitRepoPath} also exists. Redirecting master folder."); string masterRepoPossibleGitDirPath = Path.Combine(masterGitRepoPath, ".git"); if (Directory.Exists(masterRepoPossibleGitDirPath)) diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/DefaultChannelStatusOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/DefaultChannelStatusOperation.cs index 756f445c5b..689a7affa4 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/DefaultChannelStatusOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/DefaultChannelStatusOperation.cs @@ -7,7 +7,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -15,11 +14,16 @@ namespace Microsoft.DotNet.Darc.Operations; internal class DefaultChannelStatusOperation : UpdateDefaultChannelBaseOperation { private readonly DefaultChannelStatusCommandLineOptions _options; + private readonly ILogger _logger; - public DefaultChannelStatusOperation(DefaultChannelStatusCommandLineOptions options) - : base(options) + public DefaultChannelStatusOperation( + DefaultChannelStatusCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) + : base(options, barClient) { _options = options; + _logger = logger; } /// @@ -34,8 +38,6 @@ public override async Task ExecuteAsync() return Constants.ErrorCode; } - IBarApiClient barClient = Provider.GetRequiredService(); - try { DefaultChannel resolvedChannel = await ResolveSingleChannel(); @@ -64,7 +66,7 @@ public override async Task ExecuteAsync() enabled = false; } - await barClient.UpdateDefaultChannelAsync(resolvedChannel.Id, enabled: enabled); + await _barClient.UpdateDefaultChannelAsync(resolvedChannel.Id, enabled: enabled); Console.WriteLine($"Default channel association has been {(enabled ? "enabled" : "disabled")}."); @@ -77,7 +79,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed enable/disable default channel association."); + _logger.LogError(e, "Error: Failed enable/disable default channel association."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteBuildFromChannelOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteBuildFromChannelOperation.cs index 7f81c671b0..7debde2739 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteBuildFromChannelOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteBuildFromChannelOperation.cs @@ -1,25 +1,31 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Linq; +using System.Threading.Tasks; using Microsoft.DotNet.Darc.Options; using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System; -using System.Linq; -using System.Threading.Tasks; namespace Microsoft.DotNet.Darc.Operations; internal class DeleteBuildFromChannelOperation : Operation { private readonly DeleteBuildFromChannelCommandLineOptions _options; - public DeleteBuildFromChannelOperation(DeleteBuildFromChannelCommandLineOptions options) - : base(options) + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public DeleteBuildFromChannelOperation( + DeleteBuildFromChannelCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -30,17 +36,15 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - // Find the build to give someone info - Build build = await barClient.GetBuildAsync(_options.Id); + Build build = await _barClient.GetBuildAsync(_options.Id); if (build == null) { Console.WriteLine($"Could not find a build with id '{_options.Id}'"); return Constants.ErrorCode; } - Channel targetChannel = await UxHelpers.ResolveSingleChannel(barClient, _options.Channel); + Channel targetChannel = await UxHelpers.ResolveSingleChannel(_barClient, _options.Channel); if (targetChannel == null) { return Constants.ErrorCode; @@ -56,7 +60,7 @@ public override async Task ExecuteAsync() Console.WriteLine(); Console.Write(UxHelpers.GetTextBuildDescription(build)); - await barClient.DeleteBuildFromChannelAsync(_options.Id, targetChannel.Id); + await _barClient.DeleteBuildFromChannelAsync(_options.Id, targetChannel.Id); // Let the user know they can trigger subscriptions if they'd like. Console.WriteLine("Subscriptions can be triggered to revert to the previous state using the following command:"); @@ -71,7 +75,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, $"Error: Failed to delete build '{_options.Id}' from channel '{_options.Channel}'."); + _logger.LogError(e, $"Error: Failed to delete build '{_options.Id}' from channel '{_options.Channel}'."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteChannelOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteChannelOperation.cs index 4c4dae22bb..b7ffffef5d 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteChannelOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteChannelOperation.cs @@ -8,18 +8,24 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; internal class DeleteChannelOperation : Operation { + private readonly IBarApiClient _barClient; private readonly DeleteChannelCommandLineOptions _options; - public DeleteChannelOperation(DeleteChannelCommandLineOptions options) - : base(options) + private readonly ILogger _logger; + + public DeleteChannelOperation( + DeleteChannelCommandLineOptions options, + ILogger logger, + IBarApiClient barClient) { _options = options; + _logger = logger; + _barClient = barClient; } /// @@ -30,18 +36,16 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - // Get the ID of the channel with the specified name. - Channel existingChannel = (await barClient.GetChannelsAsync()).Where(channel => channel.Name.Equals(_options.Name, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + Channel existingChannel = (await _barClient.GetChannelsAsync()).Where(channel => channel.Name.Equals(_options.Name, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); if (existingChannel == null) { - Logger.LogError($"Could not find channel with name '{_options.Name}'"); + _logger.LogError($"Could not find channel with name '{_options.Name}'"); return Constants.ErrorCode; } - await barClient.DeleteChannelAsync(existingChannel.Id); + await _barClient.DeleteChannelAsync(existingChannel.Id); Console.WriteLine($"Successfully deleted channel '{existingChannel.Name}'."); return Constants.SuccessCode; @@ -53,7 +57,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to delete channel."); + _logger.LogError(e, "Error: Failed to delete channel."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteDefaultChannelOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteDefaultChannelOperation.cs index 3d982aa5a6..ad59dc3505 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteDefaultChannelOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteDefaultChannelOperation.cs @@ -7,31 +7,34 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; internal class DeleteDefaultChannelOperation : UpdateDefaultChannelBaseOperation { - public DeleteDefaultChannelOperation(DeleteDefaultChannelCommandLineOptions options) - : base(options) + private readonly ILogger _logger; + + public DeleteDefaultChannelOperation( + DeleteDefaultChannelCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) + : base(options, barClient) { + _logger = logger; } public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - DefaultChannel resolvedChannel = await ResolveSingleChannel(); if (resolvedChannel == null) { return Constants.ErrorCode; } - await barClient.DeleteDefaultChannelAsync(resolvedChannel.Id); + await _barClient.DeleteDefaultChannelAsync(resolvedChannel.Id); return Constants.SuccessCode; } @@ -42,7 +45,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed remove the default channel association."); + _logger.LogError(e, "Error: Failed remove the default channel association."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteSubscriptionOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteSubscriptionOperation.cs deleted file mode 100644 index d1d142cb43..0000000000 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteSubscriptionOperation.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.DotNet.Darc.Options; -using System; -using System.Threading.Tasks; - -namespace Microsoft.DotNet.Darc.Operations; - -internal class DeleteSubscriptionOperation(DeleteSubscriptionCommandLineOptions options) - : Operation(options) -{ - public override Task ExecuteAsync() - { - Console.WriteLine("The 'delete-subscription' command has been removed. Please use 'delete-subscriptions' instead"); - return Task.FromResult(Constants.ErrorCode); - } -} diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteSubscriptionsOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteSubscriptionsOperation.cs index a2573f0214..73597b5f3d 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteSubscriptionsOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/DeleteSubscriptionsOperation.cs @@ -10,26 +10,29 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; internal class DeleteSubscriptionsOperation : Operation { + private readonly IBarApiClient _barClient; private readonly DeleteSubscriptionsCommandLineOptions _options; - public DeleteSubscriptionsOperation(DeleteSubscriptionsCommandLineOptions options) - : base(options) + private readonly ILogger _logger; + public DeleteSubscriptionsOperation( + DeleteSubscriptionsCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - bool noConfirm = _options.NoConfirmation; List subscriptionsToDelete = []; @@ -38,7 +41,7 @@ public override async Task ExecuteAsync() // Look up subscription so we can print it later. try { - Subscription subscription = await barClient.GetSubscriptionAsync(_options.Id); + Subscription subscription = await _barClient.GetSubscriptionAsync(_options.Id); subscriptionsToDelete.Add(subscription); } catch (RestApiException e) when (e.Response.Status == (int) HttpStatusCode.NotFound) @@ -55,7 +58,7 @@ public override async Task ExecuteAsync() return Constants.ErrorCode; } - IEnumerable subscriptions = await _options.FilterSubscriptions(barClient); + IEnumerable subscriptions = await _options.FilterSubscriptions(_barClient); if (!subscriptions.Any()) { @@ -90,7 +93,7 @@ public override async Task ExecuteAsync() { Console.WriteLine($" {UxHelpers.GetSubscriptionDescription(subscription)}"); } - await barClient.DeleteSubscriptionAsync(subscription.Id); + await _barClient.DeleteSubscriptionAsync(subscription.Id); } Console.WriteLine("done"); @@ -103,7 +106,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Unexpected error while deleting subscriptions."); + _logger.LogError(e, "Unexpected error while deleting subscriptions."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GatherDropOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GatherDropOperation.cs index 9ccfc5da07..8bb9cb50ae 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GatherDropOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GatherDropOperation.cs @@ -22,7 +22,6 @@ using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; using Microsoft.DotNet.Services.Utility; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -37,10 +36,16 @@ internal class InputBuilds internal class GatherDropOperation : Operation { private readonly GatherDropCommandLineOptions _options; - private Lazy _defaultAzureCredential; - - public GatherDropOperation(GatherDropCommandLineOptions options) - : base(options) + private readonly IBarApiClient _barClient; + private readonly Lazy _defaultAzureCredential; + private readonly ILogger _logger; + private readonly IRemoteFactory _remoteFactory; + + public GatherDropOperation( + GatherDropCommandLineOptions options, + ILogger logger, + IBarApiClient barClient, + IRemoteFactory remoteFactory) { _options = options; _defaultAzureCredential = new Lazy(() => @@ -52,6 +57,9 @@ public GatherDropOperation(GatherDropCommandLineOptions options) ExcludeInteractiveBrowserCredential = _options.IsCi } )); + _logger = logger; + _barClient = barClient; + _remoteFactory = remoteFactory; } private const string PackagesSubPath = "packages"; @@ -156,7 +164,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to gather drop."); + _logger.LogError(e, "Error: Failed to gather drop."); return Constants.ErrorCode; } } @@ -164,7 +172,7 @@ public override async Task ExecuteAsync() private async Task> GetBuildDependenciesAsync(Build build) { var repoUri = build.GetRepository(); - IRemote remote = RemoteFactory.GetRemote(_options, repoUri, Logger); + IRemote remote = RemoteFactory.GetRemote(_options, repoUri, _logger); return await remote.GetDependenciesAsync(repoUri, build.Commit); } @@ -218,8 +226,6 @@ private async Task> GetRootBuildsAsync() return null; } - IBarApiClient barClient = Provider.GetRequiredService(); - string repoUri = _options.RepoUri; if (_options.RootBuildIds.Any()) @@ -228,7 +234,7 @@ private async Task> GetRootBuildsAsync() foreach (var rootBuildId in _options.RootBuildIds) { Console.WriteLine($"Looking up build by id {rootBuildId}"); - rootBuildTasks.Add(barClient.GetBuildAsync(rootBuildId)); + rootBuildTasks.Add(_barClient.GetBuildAsync(rootBuildId)); } return await Task.WhenAll(rootBuildTasks); } @@ -236,7 +242,7 @@ private async Task> GetRootBuildsAsync() { if (!string.IsNullOrEmpty(_options.Channel)) { - IEnumerable channels = await barClient.GetChannelsAsync(); + IEnumerable channels = await _barClient.GetChannelsAsync(); IEnumerable desiredChannels = channels.Where(channel => channel.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase)); if (desiredChannels.Count() != 1) { @@ -249,7 +255,7 @@ private async Task> GetRootBuildsAsync() } Channel targetChannel = desiredChannels.First(); Console.WriteLine($"Looking up latest build of '{repoUri}' on channel '{targetChannel.Name}'"); - Build rootBuild = await barClient.GetLatestBuildAsync(repoUri, targetChannel.Id); + Build rootBuild = await _barClient.GetLatestBuildAsync(repoUri, targetChannel.Id); if (rootBuild == null) { Console.WriteLine($"No build of '{repoUri}' found on channel '{targetChannel.Name}'"); @@ -260,7 +266,7 @@ private async Task> GetRootBuildsAsync() else if (!string.IsNullOrEmpty(_options.Commit)) { Console.WriteLine($"Looking up builds of {_options.RepoUri}@{_options.Commit}"); - IEnumerable builds = await barClient.GetBuildsAsync(_options.RepoUri, _options.Commit); + IEnumerable builds = await _barClient.GetBuildsAsync(_options.RepoUri, _options.Commit); // If more than one is available, print them with their IDs. if (builds.Count() > 1) { @@ -391,7 +397,7 @@ private async Task WriteDropManifestAsync(List downloadedBuilds extraAssets, _options.OutputDirectory, _options.UseRelativePathsInManifest, - Logger); + _logger); await File.WriteAllTextAsync(outputPath, JsonConvert.SerializeObject(manifestJson, Formatting.Indented)); } @@ -470,8 +476,6 @@ private async Task GatherBuildsToDownloadAsync(IEnumerable r builds.Add(rootBuild); } - var remoteFactory = Provider.GetRequiredService(); - // Flatten for convenience and remove dependencies of types that we don't want if need be. if (!_options.IncludeToolset) { @@ -492,12 +496,12 @@ private async Task GatherBuildsToDownloadAsync(IEnumerable r var rootBuildRepository = rootBuild.GetRepository(); DependencyGraph graph = await DependencyGraph.BuildRemoteDependencyGraphAsync( - remoteFactory, - Provider.GetRequiredService(), + _remoteFactory, + _barClient, rootBuildRepository, rootBuild.Commit, buildOptions, - Logger); + _logger); // Because the dependency graph starts the build from a repo+sha, it's possible // that multiple unique builds of that root repo+sha were done. But we don't want those other builds. @@ -585,7 +589,6 @@ private static string GetAssetNameForLogging(Asset asset) /// Output directory. Must exist. private async Task GatherDropForBuildAsync(Build build, string rootOutputDirectory) { - IBarApiClient barClient = Provider.GetRequiredService(); var success = true; var unifiedOutputDirectory = rootOutputDirectory; Directory.CreateDirectory(unifiedOutputDirectory); @@ -619,7 +622,7 @@ private async Task GatherDropForBuildAsync(Build build, string List mustDownloadAssets = []; string[] alwaysDownloadRegexes = _options.AlwaysDownloadAssetPatterns.Split(',', StringSplitOptions.RemoveEmptyEntries); - var assets = await barClient.GetAssetsAsync(buildId: build.Id, nonShipping: (!_options.IncludeNonShipping ? (bool?)false : null)); + var assets = await _barClient.GetAssetsAsync(buildId: build.Id, nonShipping: (!_options.IncludeNonShipping ? (bool?)false : null)); if (!string.IsNullOrEmpty(_options.AssetFilter)) { assets = assets.Where(asset => Regex.IsMatch(asset.Name, _options.AssetFilter)); @@ -935,7 +938,7 @@ private async Task DownloadAssetFromLocation(HttpClient client, switch (locationType) { case LocationType.NugetFeed: - return await DownloadNugetPackageAsync(client, build, asset, location, releaseSubPath, unifiedSubPath, errors, downloadOutput); + return await DownloadNugetPackageAsync(client, asset, location, releaseSubPath, unifiedSubPath, errors, downloadOutput); case LocationType.Container: return await DownloadBlobAsync(client, asset, location, releaseSubPath, unifiedSubPath, errors, downloadOutput); case LocationType.None: @@ -961,8 +964,8 @@ private async Task DownloadAssetFromLocation(HttpClient client, /// Directory in the release layout to download to /// Directory in the unified layout to download to /// True if package could be downloaded, false otherwise. - private async Task DownloadNugetPackageAsync(HttpClient client, - Build build, + private async Task DownloadNugetPackageAsync( + HttpClient client, Asset asset, AssetLocation assetLocation, string releaseOutputDirectory, @@ -1284,7 +1287,7 @@ private async Task DownloadBlobAsync(HttpClient client, var versionSegmentToRemove = $".{asset.Version}"; if (!replacedName.EndsWith(versionSegmentToRemove)) { - Logger.LogWarning($"Warning: Expected that '{asset.Name}', with package and path parts removed, would end in '{asset.Version}'. Instead was '{replacedName}'."); + _logger.LogWarning($"Warning: Expected that '{asset.Name}', with package and path parts removed, would end in '{asset.Version}'. Instead was '{replacedName}'."); } replacedName = replacedName.Remove(replacedName.Length - versionSegmentToRemove.Length); @@ -1438,7 +1441,7 @@ private async Task DownloadFileImplAsync(HttpClient client, client, HttpMethod.Get, sourceUri, - Logger, + _logger, authHeader: authHeader, configureRequestMessage: ConfigureRequestMessage, httpCompletionOption: HttpCompletionOption.ResponseHeadersRead); @@ -1507,7 +1510,7 @@ private void ConfigureRequestMessage(HttpRequestMessage request) if (_options.UseAzureCredentialForBlobs) { - TokenRequestContext tokenRequest = new TokenRequestContext(["https://storage.azure.com/"]); + var tokenRequest = new TokenRequestContext(["https://storage.azure.com/"]); var token = _defaultAzureCredential.Value.GetToken(tokenRequest); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetAssetOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetAssetOperation.cs index 488674799d..06a28da1fe 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetAssetOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetAssetOperation.cs @@ -10,7 +10,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -22,11 +21,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetAssetOperation : Operation { private readonly GetAssetCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public GetAssetOperation(GetAssetCommandLineOptions options) - : base(options) + public GetAssetOperation( + GetAssetCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } public override async Task ExecuteAsync() @@ -37,14 +42,12 @@ public override async Task ExecuteAsync() return Constants.ErrorCode; } - IBarApiClient barClient = Provider.GetRequiredService(); - try { Channel targetChannel = null; if (!string.IsNullOrEmpty(_options.Channel)) { - targetChannel = await UxHelpers.ResolveSingleChannel(barClient, _options.Channel); + targetChannel = await UxHelpers.ResolveSingleChannel(_barClient, _options.Channel); if (targetChannel == null) { return Constants.ErrorCode; @@ -53,7 +56,7 @@ public override async Task ExecuteAsync() // Starting with the remote, get information on the asset name + version List matchingAssets = - (await barClient.GetAssetsAsync(name: _options.Name, version: _options.Version, buildId: _options.Build)).ToList(); + (await _barClient.GetAssetsAsync(name: _options.Name, version: _options.Version, buildId: _options.Build)).ToList(); var queryDescription = new StringBuilder(); @@ -99,7 +102,7 @@ public override async Task ExecuteAsync() Build buildInfo = null; if (_options.Build.HasValue) { - buildInfo = await barClient.GetBuildAsync(_options.Build.Value); + buildInfo = await _barClient.GetBuildAsync(_options.Build.Value); } foreach (Asset asset in matchingAssets) @@ -107,7 +110,7 @@ public override async Task ExecuteAsync() // Get build info for asset if (!_options.Build.HasValue) { - buildInfo = await barClient.GetBuildAsync(asset.BuildId); + buildInfo = await _barClient.GetBuildAsync(asset.BuildId); if (now.Subtract(buildInfo.DateProduced).TotalDays > maxAgeInDays) { break; @@ -185,15 +188,8 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to retrieve information about assets."); + _logger.LogError(e, "Error: Failed to retrieve information about assets."); return Constants.ErrorCode; } } - - protected override bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch - { - DarcOutputType.json => true, - _ => base.IsOutputFormatSupported(outputFormat), - }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetBuildOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetBuildOperation.cs index 87618573b5..77e2c4e826 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetBuildOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetBuildOperation.cs @@ -9,7 +9,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -19,11 +18,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetBuildOperation : Operation { private readonly GetBuildCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public GetBuildOperation(GetBuildCommandLineOptions options, IServiceCollection? services = null) - : base(options, services) + public GetBuildOperation( + GetBuildCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -34,8 +39,6 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - List? matchingBuilds = null; if (_options.Id != 0) { @@ -46,7 +49,7 @@ public override async Task ExecuteAsync() return Constants.ErrorCode; } - matchingBuilds = [await barClient.GetBuildAsync(_options.Id)]; + matchingBuilds = [await _barClient.GetBuildAsync(_options.Id)]; } else if (!string.IsNullOrEmpty(_options.Repo) || !string.IsNullOrEmpty(_options.Commit)) { @@ -55,7 +58,7 @@ public override async Task ExecuteAsync() Console.WriteLine("--repo and --commit should be used together."); return Constants.ErrorCode; } - var subscriptions = await barClient.GetSubscriptionsAsync(); + var subscriptions = await _barClient.GetSubscriptionsAsync(); var possibleRepos = subscriptions .SelectMany(subscription => new List { subscription.SourceRepository, subscription.TargetRepository }) .Where(r => r.Contains(_options.Repo, StringComparison.OrdinalIgnoreCase)) @@ -64,7 +67,7 @@ public override async Task ExecuteAsync() matchingBuilds = []; foreach (string repo in possibleRepos) { - matchingBuilds.AddRange(await barClient.GetBuildsAsync(repo, _options.Commit)); + matchingBuilds.AddRange(await _barClient.GetBuildsAsync(repo, _options.Commit)); } matchingBuilds = matchingBuilds.DistinctBy(build => UxHelpers.GetTextBuildDescription(build)).ToList(); } @@ -115,15 +118,8 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to retrieve build information."); + _logger.LogError(e, "Error: Failed to retrieve build information."); return Constants.ErrorCode; } } - - protected override bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch - { - DarcOutputType.json => true, - _ => base.IsOutputFormatSupported(outputFormat), - }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetChannelOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetChannelOperation.cs index ef9e8851a0..de604ac6a0 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetChannelOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetChannelOperation.cs @@ -6,7 +6,6 @@ using Microsoft.DotNet.Darc.Options; using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -15,10 +14,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetChannelOperation : Operation { private readonly GetChannelCommandLineOptions _options; - public GetChannelOperation(GetChannelCommandLineOptions options) - : base(options) + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public GetChannelOperation( + GetChannelCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -30,13 +36,11 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - - var channel = await barClient.GetChannelAsync(_options.Id); + var channel = await _barClient.GetChannelAsync(_options.Id); if (channel == null) { - Logger.LogError("Channel with id {channelId} not found", _options.Id); + _logger.LogError("Channel with id {channelId} not found", _options.Id); return Constants.ErrorCode; } @@ -61,15 +65,8 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to retrieve the channel"); + _logger.LogError(e, "Error: Failed to retrieve the channel"); return Constants.ErrorCode; } } - - protected override bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch - { - DarcOutputType.json => true, - _ => base.IsOutputFormatSupported(outputFormat), - }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetChannelsOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetChannelsOperation.cs index 1dbccd7825..38d2baceb1 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetChannelsOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetChannelsOperation.cs @@ -9,7 +9,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -18,10 +17,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetChannelsOperation : Operation { private readonly GetChannelsCommandLineOptions _options; - public GetChannelsOperation(GetChannelsCommandLineOptions options) - : base(options) + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public GetChannelsOperation( + GetChannelsCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -33,9 +39,7 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - - var allChannels = await barClient.GetChannelsAsync(); + var allChannels = await _barClient.GetChannelsAsync(); switch (_options.OutputFormat) { case DarcOutputType.json: @@ -57,7 +61,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to retrieve channels"); + _logger.LogError(e, "Error: Failed to retrieve channels"); return Constants.ErrorCode; } } @@ -77,13 +81,6 @@ private static void WriteJsonChannelList(IEnumerable allChannels) Console.WriteLine(JsonConvert.SerializeObject(channelJson, Formatting.Indented)); } - protected override bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch - { - DarcOutputType.json => true, - _ => base.IsOutputFormatSupported(outputFormat), - }; - private static void WriteYamlChannelList(IEnumerable allChannels) { // Write out a simple list of each channel's name diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetDefaultChannelsOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetDefaultChannelsOperation.cs index 0ffed40ff6..eb400aa305 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetDefaultChannelsOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetDefaultChannelsOperation.cs @@ -9,7 +9,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -20,10 +19,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetDefaultChannelsOperation : Operation { private readonly GetDefaultChannelsCommandLineOptions _options; - public GetDefaultChannelsOperation(GetDefaultChannelsCommandLineOptions options) - : base(options) + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public GetDefaultChannelsOperation( + GetDefaultChannelsCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -35,9 +41,7 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - - IEnumerable defaultChannels = (await barClient.GetDefaultChannelsAsync()) + IEnumerable defaultChannels = (await _barClient.GetDefaultChannelsAsync()) .Where(defaultChannel => { return (string.IsNullOrEmpty(_options.SourceRepository) || @@ -69,7 +73,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to retrieve default channel information."); + _logger.LogError(e, "Error: Failed to retrieve default channel information."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependenciesOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependenciesOperation.cs index 3045a40a45..1d708ae2aa 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependenciesOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependenciesOperation.cs @@ -14,16 +14,19 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetDependenciesOperation : Operation { private readonly GetDependenciesCommandLineOptions _options; + private readonly ILogger _logger; - public GetDependenciesOperation(GetDependenciesCommandLineOptions options) - : base(options) + public GetDependenciesOperation( + GetDependenciesCommandLineOptions options, + ILogger logger) { _options = options; + _logger = logger; } public override async Task ExecuteAsync() { - var local = new Local(_options.GetRemoteTokenProvider(), Logger); + var local = new Local(_options.GetRemoteTokenProvider(), _logger); try { @@ -31,7 +34,9 @@ public override async Task ExecuteAsync() if (!string.IsNullOrEmpty(_options.Name)) { - DependencyDetail dependency = dependencies.Where(d => d.Name.Equals(_options.Name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + DependencyDetail dependency = dependencies + .Where(d => d.Name.Equals(_options.Name, StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefault(); if (dependency == null) { @@ -54,11 +59,11 @@ public override async Task ExecuteAsync() { if (!string.IsNullOrEmpty(_options.Name)) { - Logger.LogError(exc, $"Something failed while querying for local dependency '{_options.Name}'."); + _logger.LogError(exc, $"Something failed while querying for local dependency '{_options.Name}'."); } else { - Logger.LogError(exc, "Something failed while querying for local dependencies."); + _logger.LogError(exc, "Something failed while querying for local dependencies."); } return Constants.ErrorCode; diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependencyFlowGraphOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependencyFlowGraphOperation.cs index 7f3a4eff2f..dcaa0665b1 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependencyFlowGraphOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependencyFlowGraphOperation.cs @@ -10,7 +10,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -18,32 +17,35 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetDependencyFlowGraphOperation : Operation { private readonly GetDependencyFlowGraphCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public GetDependencyFlowGraphOperation(GetDependencyFlowGraphCommandLineOptions options) - : base(options) + public GetDependencyFlowGraphOperation( + GetDependencyFlowGraphCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } public override async Task ExecuteAsync() { try { - IRemoteFactory remoteFactory = Provider.GetRequiredService(); - IBarApiClient barClient = Provider.GetRequiredService(); - Channel targetChannel = null; if (!string.IsNullOrEmpty(_options.Channel)) { // Resolve the channel. - targetChannel = await UxHelpers.ResolveSingleChannel(barClient, _options.Channel); + targetChannel = await UxHelpers.ResolveSingleChannel(_barClient, _options.Channel); if (targetChannel == null) { return Constants.ErrorCode; } } - var flowGraph = await barClient.GetDependencyFlowGraphAsync( + var flowGraph = await _barClient.GetDependencyFlowGraphAsync( targetChannel?.Id ?? 0, _options.Days, includeArcade: true, @@ -62,7 +64,7 @@ public override async Task ExecuteAsync() } catch (Exception exc) { - Logger.LogError(exc, "Something failed while getting the dependency graph."); + _logger.LogError(exc, "Something failed while getting the dependency graph."); return Constants.ErrorCode; } } @@ -116,7 +118,7 @@ private async Task LogGraphVizAsync(Channel targetChannel, DependencyFlowGraph g await writer.WriteLineAsync(" node [shape=record]"); foreach (DependencyFlowNode node in graph.Nodes) { - StringBuilder nodeBuilder = new StringBuilder(); + var nodeBuilder = new StringBuilder(); string style = node.OnLongestBuildPath ? "style=\"diagonals,bold\" color=red" : ""; diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependencyGraphOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependencyGraphOperation.cs index ac7a35f7d4..9a7d893d21 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependencyGraphOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetDependencyGraphOperation.cs @@ -12,7 +12,6 @@ using Microsoft.DotNet.DarcLib.Helpers; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -21,17 +20,26 @@ internal class GetDependencyGraphOperation : Operation { private readonly GetDependencyGraphCommandLineOptions _options; private readonly LocalLibGit2Client _gitClient; - - public GetDependencyGraphOperation(GetDependencyGraphCommandLineOptions options) - : base(options) + private readonly IRemoteFactory _remoteFactory; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public GetDependencyGraphOperation( + GetDependencyGraphCommandLineOptions options, + ILogger logger, + IRemoteFactory remoteFactory, + IBarApiClient barClient) { _options = options; _gitClient = new LocalLibGit2Client( options.GetRemoteTokenProvider(), new NoTelemetryRecorder(), - new ProcessManager(Logger, _options.GitLocation), + new ProcessManager(logger, _options.GitLocation), new FileSystem(), - Logger); + logger); + _logger = logger; + _remoteFactory = remoteFactory; + _barClient = barClient; } public override async Task ExecuteAsync() @@ -40,7 +48,6 @@ public override async Task ExecuteAsync() { IEnumerable rootDependencies = null; DependencyGraph graph; - IRemoteFactory remoteFactory = Provider.GetRequiredService(); if (!_options.Local) { @@ -78,7 +85,7 @@ public override async Task ExecuteAsync() // Grab root dependency set. The graph build can do this, but // if an original asset name is passed, then this will do the initial filtering. - IRemote rootRepoRemote = await remoteFactory.GetRemoteAsync(_options.RepoUri, Logger); + IRemote rootRepoRemote = await _remoteFactory.GetRemoteAsync(_options.RepoUri, _logger); rootDependencies = await rootRepoRemote.GetDependenciesAsync( _options.RepoUri, _options.Version, @@ -95,7 +102,7 @@ public override async Task ExecuteAsync() Console.WriteLine($"Getting root dependencies from local repository..."); // Grab root dependency set from local repo - var local = new Local(_options.GetRemoteTokenProvider(), Logger); + var local = new Local(_options.GetRemoteTokenProvider(), _logger); rootDependencies = await local.GetDependenciesAsync( _options.AssetName); } @@ -119,19 +126,19 @@ public override async Task ExecuteAsync() // Build graph graph = await DependencyGraph.BuildRemoteDependencyGraphAsync( - remoteFactory, - Provider.GetRequiredService(), + _remoteFactory, + _barClient, rootDependencies, _options.RepoUri ?? await _gitClient.GetRootDirAsync(), _options.Version ?? await _gitClient.GetGitCommitAsync(), graphBuildOptions, - Logger); + _logger); } else { Console.WriteLine($"Getting root dependencies from local repository..."); - var local = new Local(_options.GetRemoteTokenProvider(), Logger); + var local = new Local(_options.GetRemoteTokenProvider(), _logger); rootDependencies = await local.GetDependenciesAsync( _options.AssetName); @@ -156,7 +163,7 @@ public override async Task ExecuteAsync() graph = await DependencyGraph.BuildLocalDependencyGraphAsync( rootDependencies, graphBuildOptions, - Logger, + _logger, await _gitClient.GetRootDirAsync(), await _gitClient.GetGitCommitAsync(), _options.ReposFolder, @@ -186,7 +193,7 @@ await _gitClient.GetGitCommitAsync(), } catch (Exception exc) { - Logger.LogError(exc, "Something failed while getting the dependency graph."); + _logger.LogError(exc, "Something failed while getting the dependency graph."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetGoalOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetGoalOperation.cs index 90fa789913..21fa8447be 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetGoalOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetGoalOperation.cs @@ -8,7 +8,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -16,11 +15,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetGoalOperation : Operation { private readonly GetGoalCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public GetGoalOperation(GetGoalCommandLineOptions options) - : base(options) + public GetGoalOperation( + GetGoalCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -31,8 +36,7 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - Goal goalInfo = await barClient.GetGoalAsync(_options.Channel, _options.DefinitionId); + Goal goalInfo = await _barClient.GetGoalAsync(_options.Channel, _options.DefinitionId); Console.Write(goalInfo.Minutes); return Constants.SuccessCode; } @@ -43,12 +47,12 @@ public override async Task ExecuteAsync() } catch (RestApiException e) when (e.Response.Status == (int)HttpStatusCode.NotFound) { - Logger.LogError(e, $"Cannot find Channel '{_options.Channel}'."); + _logger.LogError(e, $"Cannot find Channel '{_options.Channel}'."); return Constants.ErrorCode; } catch (Exception e) { - Logger.LogError(e, $"Unable to create goal for Channel : '{_options.Channel}' and DefinitionId : '{_options.DefinitionId}'."); + _logger.LogError(e, $"Unable to create goal for Channel : '{_options.Channel}' and DefinitionId : '{_options.DefinitionId}'."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetHealthOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetHealthOperation.cs index e1a9aa895a..730efd70e2 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetHealthOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetHealthOperation.cs @@ -11,7 +11,7 @@ using Microsoft.DotNet.DarcLib.HealthMetrics; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Services.Common; namespace Microsoft.DotNet.Darc.Operations; @@ -45,21 +45,29 @@ public HealthMetricWithOutput(HealthMetric metric, string formattedOutput) internal class GetHealthOperation : Operation { private readonly GetHealthCommandLineOptions _options; - public GetHealthOperation(GetHealthCommandLineOptions options) - : base(options) + private readonly IRemoteFactory _remoteFactory; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public GetHealthOperation( + GetHealthCommandLineOptions options, + IBarApiClient barClient, + IRemoteFactory remoteFactory, + ILogger logger) { _options = options; + _barClient = barClient; + _remoteFactory = remoteFactory; + _logger = logger; } public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - - IEnumerable subscriptions = await barClient.GetSubscriptionsAsync(); - IEnumerable defaultChannels = await barClient.GetDefaultChannelsAsync(); - IEnumerable channels = await barClient.GetChannelsAsync(); + IEnumerable subscriptions = await _barClient.GetSubscriptionsAsync(); + IEnumerable defaultChannels = await _barClient.GetDefaultChannelsAsync(); + IEnumerable channels = await _barClient.GetChannelsAsync(); HashSet channelsToEvaluate = ComputeChannelsToEvaluate(channels); HashSet reposToEvaluate = ComputeRepositoriesToEvaluate(defaultChannels, subscriptions); @@ -205,8 +213,6 @@ private List>> ComputeSubscriptionHealthMetric IEnumerable subscriptions, IEnumerable defaultChannels) { - var remoteFactory = Provider.GetRequiredService(); - var barClient = Provider.GetRequiredService(); HashSet<(string repo, string branch)> repoBranchCombinations = GetRepositoryBranchCombinations(channelsToEvaluate, reposToEvaluate, subscriptions, defaultChannels); @@ -218,9 +224,9 @@ private List>> ComputeSubscriptionHealthMetric t.repo, t.branch, d => true, - remoteFactory, - barClient, - Logger); + _remoteFactory, + _barClient, + _logger); await healthMetric.EvaluateAsync(); @@ -280,16 +286,13 @@ private List>> ComputeProductDependencyCycleMe IEnumerable subscriptions, IEnumerable defaultChannels) { - var remoteFactory = Provider.GetRequiredService(); - var barClient = Provider.GetRequiredService(); - HashSet<(string repo, string branch)> repoBranchCombinations = GetRepositoryBranchCombinations(channelsToEvaluate, reposToEvaluate, subscriptions, defaultChannels); return repoBranchCombinations.Select<(string repo, string branch), Func>>(t => async () => { - var healthMetric = new ProductDependencyCyclesHealthMetric(t.repo, t.branch, remoteFactory, barClient, Logger); + var healthMetric = new ProductDependencyCyclesHealthMetric(t.repo, t.branch, _remoteFactory, _barClient, _logger); await healthMetric.EvaluateAsync(); diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetLatestBuildOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetLatestBuildOperation.cs index 5aa5d69131..ba6ab5a9c3 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetLatestBuildOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetLatestBuildOperation.cs @@ -9,7 +9,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -17,10 +16,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetLatestBuildOperation : Operation { private readonly GetLatestBuildCommandLineOptions _options; - public GetLatestBuildOperation(GetLatestBuildCommandLineOptions options) - : base(options) + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public GetLatestBuildOperation( + GetLatestBuildCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -31,8 +37,6 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - // Calculate out possible repos based on the input strings. // Today the DB has no way of searching for builds by substring, so for now // grab source/targets repos of subscriptions matched on substring, @@ -40,14 +44,14 @@ public override async Task ExecuteAsync() // Then search channels by substring // Then run GetLatestBuild for each permutation. - var subscriptions = await barClient.GetSubscriptionsAsync(); + var subscriptions = await _barClient.GetSubscriptionsAsync(); var possibleRepos = subscriptions .SelectMany(subscription => new List { subscription.SourceRepository, subscription.TargetRepository }) .Where(r => r.Contains(_options.Repo, StringComparison.OrdinalIgnoreCase)) .ToHashSet(StringComparer.OrdinalIgnoreCase); possibleRepos.Add(_options.Repo); - var channels = (await barClient.GetChannelsAsync()) + var channels = (await _barClient.GetChannelsAsync()) .Where(c => string.IsNullOrEmpty(_options.Channel) || c.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase)); if (!channels.Any()) @@ -61,7 +65,7 @@ public override async Task ExecuteAsync() { foreach (Channel channel in channels) { - Build latestBuild = await barClient.GetLatestBuildAsync(possibleRepo, channel.Id); + Build latestBuild = await _barClient.GetLatestBuildAsync(possibleRepo, channel.Id); if (latestBuild != null) { if (foundBuilds) @@ -89,7 +93,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to retrieve latest build."); + _logger.LogError(e, "Error: Failed to retrieve latest build."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetRepositoryMergePoliciesOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetRepositoryMergePoliciesOperation.cs index 9412e7c3e6..1ebeaaa553 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetRepositoryMergePoliciesOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetRepositoryMergePoliciesOperation.cs @@ -9,7 +9,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -17,20 +16,24 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetRepositoryMergePoliciesOperation : Operation { private readonly GetRepositoryMergePoliciesCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public GetRepositoryMergePoliciesOperation(GetRepositoryMergePoliciesCommandLineOptions options) - : base(options) + public GetRepositoryMergePoliciesOperation( + GetRepositoryMergePoliciesCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - - IEnumerable allRepositories = await barClient.GetRepositoriesAsync(null, null); + IEnumerable allRepositories = await _barClient.GetRepositoriesAsync(null, null); IEnumerable filteredRepositories = allRepositories.Where(repositories => (string.IsNullOrEmpty(_options.Repo) || repositories.Repository.Contains(_options.Repo, StringComparison.OrdinalIgnoreCase)) && (string.IsNullOrEmpty(_options.Branch) || repositories.Branch.Contains(_options.Branch, StringComparison.OrdinalIgnoreCase))); @@ -39,7 +42,7 @@ public override async Task ExecuteAsync() // passes --all. if (!_options.All) { - HashSet batchableTargets = (await barClient.GetSubscriptionsAsync()) + HashSet batchableTargets = (await _barClient.GetSubscriptionsAsync()) .Where(s => s.Policy.Batchable) .Select(s => $"{s.TargetRepository}{s.TargetBranch}") .ToHashSet(StringComparer.OrdinalIgnoreCase); @@ -71,7 +74,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to retrieve repositories"); + _logger.LogError(e, "Error: Failed to retrieve repositories"); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/GetSubscriptionsOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/GetSubscriptionsOperation.cs index 551b79c7fc..3bfa26f9ea 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/GetSubscriptionsOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/GetSubscriptionsOperation.cs @@ -10,7 +10,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -23,20 +22,24 @@ namespace Microsoft.DotNet.Darc.Operations; internal class GetSubscriptionsOperation : Operation { private readonly GetSubscriptionsCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public GetSubscriptionsOperation(GetSubscriptionsCommandLineOptions options, IServiceCollection? services = null) - : base(options, services) + public GetSubscriptionsOperation( + GetSubscriptionsCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - - IEnumerable subscriptions = await _options.FilterSubscriptions(barClient); + IEnumerable subscriptions = await _options.FilterSubscriptions(_barClient); if (!subscriptions.Any()) { @@ -47,10 +50,10 @@ public override async Task ExecuteAsync() switch (_options.OutputFormat) { case DarcOutputType.json: - await OutputJsonAsync(subscriptions, barClient); + await OutputJsonAsync(subscriptions, _barClient); break; case DarcOutputType.text: - await OutputTextAsync(subscriptions, barClient); + await OutputTextAsync(subscriptions, _barClient); break; default: throw new NotImplementedException($"Output type {_options.OutputFormat} not supported by get-subscriptions"); @@ -65,18 +68,11 @@ public override async Task ExecuteAsync() } catch (Exception ex) { - Logger.LogError(ex, "Error: Failed to retrieve subscriptions"); + _logger.LogError(ex, "Error: Failed to retrieve subscriptions"); return Constants.ErrorCode; } } - protected override bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch - { - DarcOutputType.json => true, - _ => base.IsOutputFormatSupported(outputFormat), - }; - private static async Task OutputJsonAsync(IEnumerable subscriptions, IBarApiClient barClient) { foreach (var subscription in Sort(subscriptions)) diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/Operation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/Operation.cs index dbf00aae9b..870a3deee2 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/Operation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/Operation.cs @@ -1,101 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Threading.Tasks; -using Maestro.Common; -using Maestro.Common.AzureDevOpsTokens; -using Microsoft.Arcade.Common; -using Microsoft.DotNet.Darc.Helpers; -using Microsoft.DotNet.Darc.Options; -using Microsoft.DotNet.DarcLib; -using Microsoft.DotNet.DarcLib.Helpers; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Console; #nullable enable namespace Microsoft.DotNet.Darc.Operations; -public abstract class Operation : IDisposable +public abstract class Operation { - protected ServiceProvider Provider { get; } - - protected ILogger Logger { get; } - - protected Operation(ICommandLineOptions options, IServiceCollection? services = null) - { - // Because the internal logging in DarcLib tends to be chatty and non-useful, - // we remap the --verbose switch onto 'info', --debug onto highest level, and the - // default level onto warning - LogLevel level = LogLevel.Warning; - if (options.Debug) - { - level = LogLevel.Debug; - } - else if (options.Verbose) - { - level = LogLevel.Information; - } - - if (!IsOutputFormatSupported(options.OutputFormat)) - { - throw new NotImplementedException($"Output format type '{options.OutputFormat}' not yet supported for this operation.\r\nPlease raise a new issue in https://github.com/dotnet/arcade/issues/."); - } - - services ??= new ServiceCollection(); - services.AddLogging(b => b - .AddConsole(o => o.FormatterName = CompactConsoleLoggerFormatter.FormatterName) - .AddConsoleFormatter() - .SetMinimumLevel(level)); - - services.AddSingleton(options); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddTransient(sp => ActivatorUtilities.CreateInstance(sp, options.GitLocation)); - services.TryAddSingleton(sp => RemoteFactory.GetBarClient(options, sp.GetRequiredService>())); - services.TryAddSingleton(sp => sp.GetRequiredService()); - services.TryAddTransient(sp => sp.GetRequiredService>()); - services.TryAddTransient(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddTransient(sp => sp.GetRequiredService>()); - services.AddSingleton(_ => options.GetAzdoTokenProvider()); - services.TryAddSingleton(_ => new RemoteTokenProvider(options.AzureDevOpsPat, options.GitHubPat)); - - Provider = services.BuildServiceProvider(); - Logger = Provider.GetRequiredService>(); - options.InitializeFromSettings(Logger); - } - public abstract Task ExecuteAsync(); - - /// - /// Indicates whether the requested output format is supported. - /// - /// The desired output format. - /// - /// The base implementations returns for ; otherwise . - /// - protected virtual bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch - { - DarcOutputType.text => true, - _ => false - }; - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - Provider?.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/SetGoalOperations.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/SetGoalOperations.cs index e3ddf243b5..57b1478d20 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/SetGoalOperations.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/SetGoalOperations.cs @@ -8,7 +8,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -16,11 +15,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class SetGoalOperation : Operation { private readonly SetGoalCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public SetGoalOperation(SetGoalCommandLineOptions options) - : base(options) + public SetGoalOperation( + SetGoalCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -31,8 +36,7 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - Goal goalInfo = await barClient.SetGoalAsync(_options.Channel, _options.DefinitionId, _options.Minutes); + Goal goalInfo = await _barClient.SetGoalAsync(_options.Channel, _options.DefinitionId, _options.Minutes); Console.Write(goalInfo.Minutes); return Constants.SuccessCode; } @@ -43,12 +47,12 @@ public override async Task ExecuteAsync() } catch (RestApiException e) when (e.Response.Status == (int) HttpStatusCode.NotFound) { - Logger.LogError(e, $"Cannot find Channel '{_options.Channel}'."); + _logger.LogError(e, $"Cannot find Channel '{_options.Channel}'."); return Constants.ErrorCode; } catch (Exception e) { - Logger.LogError(e, $"Unable to create goal for Channel : '{_options.Channel}' and DefinitionId : '{_options.DefinitionId}'."); + _logger.LogError(e, $"Unable to create goal for Channel : '{_options.Channel}' and DefinitionId : '{_options.DefinitionId}'."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/SetRepositoryMergePoliciesOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/SetRepositoryMergePoliciesOperation.cs index 0d08020ba9..c2c24c234e 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/SetRepositoryMergePoliciesOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/SetRepositoryMergePoliciesOperation.cs @@ -14,7 +14,6 @@ using Microsoft.DotNet.DarcLib.Helpers; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; @@ -23,17 +22,21 @@ namespace Microsoft.DotNet.Darc.Operations; internal class SetRepositoryMergePoliciesOperation : Operation { private readonly SetRepositoryMergePoliciesCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public SetRepositoryMergePoliciesOperation(SetRepositoryMergePoliciesCommandLineOptions options) - : base(options) + public SetRepositoryMergePoliciesOperation( + SetRepositoryMergePoliciesCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } public override async Task ExecuteAsync() { - IBarApiClient barClient = Provider.GetRequiredService(); - if (_options.IgnoreChecks.Any() && !_options.AllChecksSuccessfulMergePolicy) { Console.WriteLine($"--ignore-checks must be combined with --all-checks-passed"); @@ -101,7 +104,7 @@ public override async Task ExecuteAsync() if (string.IsNullOrEmpty(repository) || string.IsNullOrEmpty(branch)) { - Logger.LogError($"Missing input parameters for merge policies. Please see command help or remove --quiet/-q for interactive mode"); + _logger.LogError($"Missing input parameters for merge policies. Please see command help or remove --quiet/-q for interactive mode"); return Constants.ErrorCode; } } @@ -111,19 +114,19 @@ public override async Task ExecuteAsync() // specify policies on the command line. In this case, they typically want to update if (!mergePolicies.Any() && !string.IsNullOrEmpty(repository) && !string.IsNullOrEmpty(branch)) { - mergePolicies = (await barClient.GetRepositoryMergePoliciesAsync(repository, branch)).ToList(); + mergePolicies = (await _barClient.GetRepositoryMergePoliciesAsync(repository, branch)).ToList(); } // Help the user along with a form. We'll use the API to gather suggested values // from existing subscriptions based on the input parameters. var initEditorPopUp = new SetRepositoryMergePoliciesPopUp("set-policies/set-policies-todo", - Logger, + _logger, repository, branch, mergePolicies, Constants.AvailableMergePolicyYamlHelp); - var uxManager = new UxManager(_options.GitLocation, Logger); + var uxManager = new UxManager(_options.GitLocation, _logger); int exitCode = uxManager.PopUp(initEditorPopUp); if (exitCode != Constants.SuccessCode) { @@ -134,8 +137,8 @@ public override async Task ExecuteAsync() mergePolicies = initEditorPopUp.MergePolicies; } - IRemote verifyRemote = RemoteFactory.GetRemote(_options, repository, Logger); - IEnumerable targetRepository = await barClient.GetRepositoriesAsync(repository, branch: null); + IRemote verifyRemote = RemoteFactory.GetRemote(_options, repository, _logger); + IEnumerable targetRepository = await _barClient.GetRepositoriesAsync(repository, branch: null); if (targetRepository == null || !targetRepository.Any()) { @@ -151,7 +154,7 @@ public override async Task ExecuteAsync() try { - await barClient.SetRepositoryMergePoliciesAsync( + await _barClient.SetRepositoryMergePoliciesAsync( repository, branch, mergePolicies); Console.WriteLine($"Successfully updated merge policies for {repository}@{branch}."); return Constants.SuccessCode; @@ -163,12 +166,12 @@ await barClient.SetRepositoryMergePoliciesAsync( } catch (RestApiException e) when (e.Response.Status == (int) System.Net.HttpStatusCode.BadRequest) { - Logger.LogError($"Failed to set repository auto merge policies: {e.Response.Content}"); + _logger.LogError($"Failed to set repository auto merge policies: {e.Response.Content}"); return Constants.ErrorCode; } catch (Exception e) { - Logger.LogError(e, $"Failed to set merge policies."); + _logger.LogError(e, $"Failed to set merge policies."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/SubscriptionsStatusOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/SubscriptionsStatusOperation.cs index 6b85267bda..d2658d6366 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/SubscriptionsStatusOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/SubscriptionsStatusOperation.cs @@ -10,7 +10,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -18,11 +17,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class SubscriptionsStatusOperation : Operation { private readonly SubscriptionsStatusCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public SubscriptionsStatusOperation(SubscriptionsStatusCommandLineOptions options) - : base(options) + public SubscriptionsStatusOperation( + SubscriptionsStatusCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -44,8 +49,6 @@ public override async Task ExecuteAsync() try { - IBarApiClient barClient = Provider.GetRequiredService(); - bool noConfirm = _options.NoConfirmation; List subscriptionsToEnableDisable = []; @@ -54,7 +57,7 @@ public override async Task ExecuteAsync() // Look up subscription so we can print it later. try { - Subscription subscription = await barClient.GetSubscriptionAsync(_options.Id); + Subscription subscription = await _barClient.GetSubscriptionAsync(_options.Id); subscriptionsToEnableDisable.Add(subscription); } catch (RestApiException e) when (e.Response.Status == (int)HttpStatusCode.NotFound) @@ -71,7 +74,7 @@ public override async Task ExecuteAsync() return Constants.ErrorCode; } - IEnumerable subscriptions = await _options.FilterSubscriptions(barClient); + IEnumerable subscriptions = await _options.FilterSubscriptions(_barClient); if (!subscriptions.Any()) { @@ -127,7 +130,7 @@ public override async Task ExecuteAsync() subscriptionToUpdate.Policy.UpdateFrequency = subscription.Policy.UpdateFrequency; subscriptionToUpdate.Policy.MergePolicies = subscription.Policy.MergePolicies; - var updatedSubscription = await barClient.UpdateSubscriptionAsync( + var updatedSubscription = await _barClient.UpdateSubscriptionAsync( subscription.Id.ToString(), subscriptionToUpdate); } @@ -142,7 +145,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, $"Unexpected error while {actionStatusMessage.ToLower()} subscriptions."); + _logger.LogError(e, $"Unexpected error while {actionStatusMessage.ToLower()} subscriptions."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/TriggerSubscriptionsOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/TriggerSubscriptionsOperation.cs index b235795d24..3f8c68ce11 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/TriggerSubscriptionsOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/TriggerSubscriptionsOperation.cs @@ -10,7 +10,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -18,10 +17,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class TriggerSubscriptionsOperation : Operation { private readonly TriggerSubscriptionsCommandLineOptions _options; - public TriggerSubscriptionsOperation(TriggerSubscriptionsCommandLineOptions options) - : base(options) + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public TriggerSubscriptionsOperation( + TriggerSubscriptionsCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -32,8 +38,6 @@ public override async Task ExecuteAsync() { try { - IBarApiClient barClient = Provider.GetRequiredService(); - bool noConfirm = _options.NoConfirmation; List subscriptionsToTrigger = []; @@ -42,7 +46,7 @@ public override async Task ExecuteAsync() // Look up subscription so we can print it later. try { - Subscription subscription = await barClient.GetSubscriptionAsync(_options.Id); + Subscription subscription = await _barClient.GetSubscriptionAsync(_options.Id); subscriptionsToTrigger.Add(subscription); } catch (RestApiException e) when (e.Response.Status == (int) HttpStatusCode.NotFound) @@ -59,7 +63,7 @@ public override async Task ExecuteAsync() return Constants.ErrorCode; } - IEnumerable subscriptions = await _options.FilterSubscriptions(barClient); + IEnumerable subscriptions = await _options.FilterSubscriptions(_barClient); if (!subscriptions.Any()) { @@ -72,7 +76,7 @@ public override async Task ExecuteAsync() if (_options.Build != 0) { - var specificBuild = await barClient.GetBuildAsync(_options.Build); + var specificBuild = await _barClient.GetBuildAsync(_options.Build); if (specificBuild == null) { Console.WriteLine($"No build found in the BAR with id '{_options.Build}'"); @@ -134,11 +138,11 @@ public override async Task ExecuteAsync() } if (_options.Build > 0) { - await barClient.TriggerSubscriptionAsync(subscription.Id, _options.Build); + await _barClient.TriggerSubscriptionAsync(subscription.Id, _options.Build); } else { - await barClient.TriggerSubscriptionAsync(subscription.Id); + await _barClient.TriggerSubscriptionAsync(subscription.Id); } } Console.WriteLine("done"); @@ -152,7 +156,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Unexpected error while triggering subscriptions."); + _logger.LogError(e, "Unexpected error while triggering subscriptions."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateBuildOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateBuildOperation.cs index 8fcbaeb6f7..bbfe65f810 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateBuildOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateBuildOperation.cs @@ -7,7 +7,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -15,10 +14,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class UpdateBuildOperation : Operation { private readonly UpdateBuildCommandLineOptions _options; - public UpdateBuildOperation(UpdateBuildCommandLineOptions options) - : base(options) + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; + + public UpdateBuildOperation( + UpdateBuildCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } public override async Task ExecuteAsync() @@ -31,9 +37,7 @@ public override async Task ExecuteAsync() try { - IBarApiClient barClient = Provider.GetRequiredService(); - - Build updatedBuild = await barClient.UpdateBuildAsync(_options.Id, new BuildUpdate { Released = _options.Released }); + Build updatedBuild = await _barClient.UpdateBuildAsync(_options.Id, new BuildUpdate { Released = _options.Released }); Console.WriteLine($"Updated build {_options.Id} with new information."); Console.WriteLine(UxHelpers.GetTextBuildDescription(updatedBuild)); @@ -45,7 +49,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, $"Error: Failed to update build with id '{_options.Id}'"); + _logger.LogError(e, $"Error: Failed to update build with id '{_options.Id}'"); return Constants.ErrorCode; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateDefaultChannelBaseOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateDefaultChannelBaseOperation.cs index 6ddfdd06ab..b80f107c3e 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateDefaultChannelBaseOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateDefaultChannelBaseOperation.cs @@ -9,19 +9,18 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client.Models; using Microsoft.DotNet.Services.Utility; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.Darc.Operations; internal abstract class UpdateDefaultChannelBaseOperation : Operation - { - private readonly UpdateDefaultChannelBaseCommandLineOptions _options; + protected readonly IBarApiClient _barClient; + private readonly IUpdateDefaultChannelBaseCommandLineOptions _options; - public UpdateDefaultChannelBaseOperation(UpdateDefaultChannelBaseCommandLineOptions options) - : base(options) + public UpdateDefaultChannelBaseOperation(IUpdateDefaultChannelBaseCommandLineOptions options, IBarApiClient barClient) { _options = options; + _barClient = barClient; } /// @@ -31,9 +30,7 @@ public UpdateDefaultChannelBaseOperation(UpdateDefaultChannelBaseCommandLineOpti /// Default channel or null protected async Task ResolveSingleChannel() { - IBarApiClient barClient = Provider.GetRequiredService(); - - IEnumerable potentialDefaultChannels = await barClient.GetDefaultChannelsAsync(); + IEnumerable potentialDefaultChannels = await _barClient.GetDefaultChannelsAsync(); // User should have supplied id or a combo of the channel name, repo, and branch. if (_options.Id != -1) diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateDependenciesOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateDependenciesOperation.cs index 99140df331..3bf1ca5811 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateDependenciesOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateDependenciesOperation.cs @@ -13,7 +13,6 @@ using Microsoft.DotNet.DarcLib.Helpers; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NuGet.Packaging; @@ -22,10 +21,23 @@ namespace Microsoft.DotNet.Darc.Operations; internal class UpdateDependenciesOperation : Operation { private readonly UpdateDependenciesCommandLineOptions _options; - public UpdateDependenciesOperation(UpdateDependenciesCommandLineOptions options) - : base(options) + private readonly ILogger _logger; + private readonly IBarApiClient _barClient; + private readonly IRemoteFactory _remoteFactory; + private readonly IGitRepoFactory _gitRepoFactory; + + public UpdateDependenciesOperation( + UpdateDependenciesCommandLineOptions options, + IBarApiClient barClient, + IRemoteFactory remoteFactory, + IGitRepoFactory gitRepoFactory, + ILogger logger) { _options = options; + _logger = logger; + _barClient = barClient; + _remoteFactory = remoteFactory; + _gitRepoFactory = gitRepoFactory; } /// @@ -37,11 +49,9 @@ public override async Task ExecuteAsync() { try { - IRemoteFactory remoteFactory = Provider.GetRequiredService(); - IBarApiClient barClient = Provider.GetRequiredService(); - var coherencyUpdateResolver = new CoherencyUpdateResolver(barClient, Logger); + var coherencyUpdateResolver = new CoherencyUpdateResolver(_barClient, _logger); - var local = new Local(_options.GetRemoteTokenProvider(), Logger); + var local = new Local(_options.GetRemoteTokenProvider(), _logger); List dependenciesToUpdate = []; bool someUpToDate = false; string finalMessage = $"Local dependencies updated from channel '{_options.Channel}'."; @@ -65,7 +75,7 @@ public override async Task ExecuteAsync() if (!candidateDependenciesForUpdate.Any()) { - Logger.LogWarning("Found no dependencies to update."); + _logger.LogWarning("Found no dependencies to update."); return Constants.ErrorCode; } @@ -87,7 +97,7 @@ public override async Task ExecuteAsync() } catch (DarcException exc) { - Logger.LogError(exc, "Failed to update dependencies based on folder '{folder}'", _options.PackagesFolder); + _logger.LogError(exc, "Failed to update dependencies based on folder '{folder}'", _options.PackagesFolder); return Constants.ErrorCode; } @@ -100,12 +110,12 @@ public override async Task ExecuteAsync() if (!_options.CoherencyOnly) { Console.WriteLine($"Looking up build with BAR id {_options.BARBuildId}"); - var specificBuild = await barClient.GetBuildAsync(_options.BARBuildId); + var specificBuild = await _barClient.GetBuildAsync(_options.BARBuildId); int nonCoherencyResult = NonCoherencyUpdatesForBuild(specificBuild, coherencyUpdateResolver, currentDependencies, candidateDependenciesForUpdate, dependenciesToUpdate); if (nonCoherencyResult != Constants.SuccessCode) { - Logger.LogError("Failed to update non-coherent parent tied dependencies."); + _logger.LogError("Failed to update non-coherent parent tied dependencies."); return nonCoherencyResult; } @@ -120,7 +130,7 @@ public override async Task ExecuteAsync() } catch (RestApiException e) when (e.Response.Status == 404) { - Logger.LogError("Could not find build with BAR id '{id}'.", _options.BARBuildId); + _logger.LogError("Could not find build with BAR id '{id}'.", _options.BARBuildId); return Constants.ErrorCode; } } @@ -128,13 +138,13 @@ public override async Task ExecuteAsync() { if (string.IsNullOrEmpty(_options.Channel)) { - Logger.LogError("Please supply either a channel name (--channel), a packages folder (--packages-folder) " + + _logger.LogError("Please supply either a channel name (--channel), a packages folder (--packages-folder) " + "a BAR build id (--id), or a specific dependency name and version (--name and --version)."); return Constants.ErrorCode; } // Start channel query. - Task channel = barClient.GetChannelAsync(_options.Channel); + Task channel = _barClient.GetChannelAsync(_options.Channel); // Limit the number of BAR queries by grabbing the repo URIs and making a hash set. // We gather the latest build for any dependencies that aren't marked with coherent parent @@ -149,14 +159,14 @@ public override async Task ExecuteAsync() Channel channelInfo = await channel; if (channelInfo == null) { - Logger.LogError("Could not find a channel named '{channel}'.", _options.Channel); + _logger.LogError("Could not find a channel named '{channel}'.", _options.Channel); return Constants.ErrorCode; } foreach (string repoToQuery in repositoryUrisForQuery) { Console.WriteLine($"Looking up latest build of {repoToQuery} on {_options.Channel}"); - var latestBuild = barClient.GetLatestBuildAsync(repoToQuery, channelInfo.Id); + var latestBuild = _barClient.GetLatestBuildAsync(repoToQuery, channelInfo.Id); getLatestBuildTaskDictionary.TryAdd(repoToQuery, latestBuild); } @@ -170,7 +180,7 @@ public override async Task ExecuteAsync() if (build == null) { - Logger.LogTrace("No build of '{uri}' found on channel '{channel}'.", + _logger.LogTrace("No build of '{uri}' found on channel '{channel}'.", repoUri, _options.Channel); continue; @@ -179,17 +189,17 @@ public override async Task ExecuteAsync() int nonCoherencyResult = NonCoherencyUpdatesForBuild(build, coherencyUpdateResolver, currentDependencies, candidateDependenciesForUpdate, dependenciesToUpdate); if (nonCoherencyResult != Constants.SuccessCode) { - Logger.LogError("Failed to update non-coherent parent tied dependencies."); + _logger.LogError("Failed to update non-coherent parent tied dependencies."); return nonCoherencyResult; } } } - int coherencyResult = await CoherencyUpdatesAsync(coherencyUpdateResolver, remoteFactory, currentDependencies, dependenciesToUpdate) + int coherencyResult = await CoherencyUpdatesAsync(coherencyUpdateResolver, _remoteFactory, currentDependencies, dependenciesToUpdate) .ConfigureAwait(false); if (coherencyResult != Constants.SuccessCode) { - Logger.LogError("Failed to update coherent parent tied dependencies."); + _logger.LogError("Failed to update coherent parent tied dependencies."); return coherencyResult; } @@ -205,7 +215,7 @@ public override async Task ExecuteAsync() } else { - Logger.LogError("Found no dependencies to update."); + _logger.LogError("Found no dependencies to update."); return Constants.ErrorCode; } } @@ -216,8 +226,7 @@ public override async Task ExecuteAsync() } // Now call the local updater to run the update - var gitRepoFactory = ActivatorUtilities.CreateInstance(Provider, Path.GetTempPath()); - await local.UpdateDependenciesAsync(dependenciesToUpdate, remoteFactory, gitRepoFactory, barClient); + await local.UpdateDependenciesAsync(dependenciesToUpdate, _remoteFactory, _gitRepoFactory, _barClient); Console.WriteLine(finalMessage); @@ -225,17 +234,17 @@ public override async Task ExecuteAsync() } catch (AuthenticationException e) { - Logger.LogError(e.Message); + _logger.LogError(e.Message); return Constants.ErrorCode; } catch (Octokit.AuthorizationException) { - Logger.LogError("Failed to update dependencies - GitHub token is invalid."); + _logger.LogError("Failed to update dependencies - GitHub token is invalid."); return Constants.ErrorCode; } catch (Exception e) { - Logger.LogError(e, "Failed to update dependencies."); + _logger.LogError(e, "Failed to update dependencies."); return Constants.ErrorCode; } } @@ -332,7 +341,7 @@ private void PrettyPrintCoherencyErrors(DarcCoherencyException e) } } - Logger.LogError(errorMessage.ToString()); + _logger.LogError(errorMessage.ToString()); } private static IEnumerable GetDependenciesFromPackagesFolder(string pathToFolder, IEnumerable dependencies) diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateSubscriptionOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateSubscriptionOperation.cs index 49de420a4e..938e7117e1 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateSubscriptionOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/UpdateSubscriptionOperation.cs @@ -12,7 +12,6 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Operations; @@ -20,11 +19,17 @@ namespace Microsoft.DotNet.Darc.Operations; internal class UpdateSubscriptionOperation : Operation { private readonly UpdateSubscriptionCommandLineOptions _options; + private readonly IBarApiClient _barClient; + private readonly ILogger _logger; - public UpdateSubscriptionOperation(UpdateSubscriptionCommandLineOptions options) - : base(options) + public UpdateSubscriptionOperation( + UpdateSubscriptionCommandLineOptions options, + IBarApiClient barClient, + ILogger logger) { _options = options; + _barClient = barClient; + _logger = logger; } /// @@ -32,14 +37,12 @@ public UpdateSubscriptionOperation(UpdateSubscriptionCommandLineOptions options) /// public override async Task ExecuteAsync() { - IBarApiClient barClient = Provider.GetRequiredService(); - // First, try to get the subscription. If it doesn't exist the call will throw and the exception will be // caught by `RunOperation` - Subscription subscription = await barClient.GetSubscriptionAsync(_options.Id); + Subscription subscription = await _barClient.GetSubscriptionAsync(_options.Id); - var suggestedRepos = barClient.GetSubscriptionsAsync(); - var suggestedChannels = barClient.GetChannelsAsync(); + var suggestedRepos = _barClient.GetSubscriptionsAsync(); + var suggestedChannels = _barClient.GetChannelsAsync(); string channel = subscription.Channel.Name; string sourceRepository = subscription.SourceRepository; @@ -71,7 +74,7 @@ public override async Task ExecuteAsync() { if (!Constants.AvailableFrequencies.Contains(_options.UpdateFrequency, StringComparer.OrdinalIgnoreCase)) { - Logger.LogError($"Unknown update frequency '{_options.UpdateFrequency}'. Available options: {string.Join(',', Constants.AvailableFrequencies)}"); + _logger.LogError($"Unknown update frequency '{_options.UpdateFrequency}'. Available options: {string.Join(',', Constants.AvailableFrequencies)}"); return 1; } updateFrequency = _options.UpdateFrequency; @@ -110,7 +113,7 @@ public override async Task ExecuteAsync() { var updateSubscriptionPopUp = new UpdateSubscriptionPopUp( "update-subscription/update-subscription-todo", - Logger, + _logger, subscription, (await suggestedChannels).Select(suggestedChannel => suggestedChannel.Name), (await suggestedRepos).SelectMany(subs => new List { subscription.SourceRepository, subscription.TargetRepository }).ToHashSet(), @@ -122,7 +125,7 @@ public override async Task ExecuteAsync() targetDirectory, excludedAssets); - var uxManager = new UxManager(_options.GitLocation, Logger); + var uxManager = new UxManager(_options.GitLocation, _logger); int exitCode = uxManager.PopUp(updateSubscriptionPopUp); @@ -169,7 +172,7 @@ public override async Task ExecuteAsync() subscriptionToUpdate.Policy.UpdateFrequency = Enum.Parse(updateFrequency, true); subscriptionToUpdate.Policy.MergePolicies = mergePolicies?.ToImmutableList(); - var updatedSubscription = await barClient.UpdateSubscriptionAsync( + var updatedSubscription = await _barClient.UpdateSubscriptionAsync( _options.Id, subscriptionToUpdate); @@ -194,7 +197,7 @@ public override async Task ExecuteAsync() if (triggerAutomatically) { - await barClient.TriggerSubscriptionAsync(updatedSubscription.Id); + await _barClient.TriggerSubscriptionAsync(updatedSubscription.Id); Console.WriteLine($"Subscription '{updatedSubscription.Id}' triggered."); } } @@ -209,12 +212,12 @@ public override async Task ExecuteAsync() catch (RestApiException e) when (e.Response.Status == (int) System.Net.HttpStatusCode.BadRequest) { // Could have been some kind of validation error (e.g. channel doesn't exist) - Logger.LogError($"Failed to update subscription: {e.Response.Content}"); + _logger.LogError($"Failed to update subscription: {e.Response.Content}"); return Constants.ErrorCode; } catch (Exception e) { - Logger.LogError(e, $"Failed to update subscription."); + _logger.LogError(e, $"Failed to update subscription."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VerifyOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VerifyOperation.cs index 5f11943d51..4babd77ce1 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VerifyOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VerifyOperation.cs @@ -12,11 +12,14 @@ namespace Microsoft.DotNet.Darc.Operations; internal class VerifyOperation : Operation { private readonly VerifyCommandLineOptions _options; + private readonly ILogger _logger; - public VerifyOperation(VerifyCommandLineOptions options) - : base(options) + public VerifyOperation( + VerifyCommandLineOptions options, + ILogger logger) { _options = options; + _logger = logger; } /// @@ -26,11 +29,11 @@ public VerifyOperation(VerifyCommandLineOptions options) /// Process exit code. public override async Task ExecuteAsync() { - var local = new Local(_options.GetRemoteTokenProvider(), Logger); + var local = new Local(_options.GetRemoteTokenProvider(), _logger); try { - if (!(await local.Verify())) + if (!await local.Verify()) { Console.WriteLine("Dependency verification failed."); return Constants.ErrorCode; @@ -40,7 +43,7 @@ public override async Task ExecuteAsync() } catch (Exception e) { - Logger.LogError(e, "Error: Failed to verify repository dependency state."); + _logger.LogError(e, "Error: Failed to verify repository dependency state."); return Constants.ErrorCode; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/BackflowOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/BackflowOperation.cs index 58e01d5680..e8e1de5da6 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/BackflowOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/BackflowOperation.cs @@ -6,13 +6,17 @@ using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.Helpers; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; #nullable enable namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; -internal class BackflowOperation(BackflowCommandLineOptions options) - : CodeFlowOperation(options) +internal class BackflowOperation( + BackflowCommandLineOptions options, + IVmrBackFlower vmrBackFlower, + IVmrInfo vmrInfo, + ILogger logger) + : CodeFlowOperation(options, vmrInfo, logger) { private readonly BackflowCommandLineOptions _options = options; @@ -21,8 +25,7 @@ protected override async Task FlowAsync( NativePath targetDirectory, string? shaToFlow, CancellationToken cancellationToken) - => await Provider.GetRequiredService() - .FlowBackAsync( + => await vmrBackFlower.FlowBackAsync( mappingName, new NativePath(targetDirectory), shaToFlow, diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/CloakedFileScanOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/CloakedFileScanOperation.cs index 2e802fe486..d63d5419da 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/CloakedFileScanOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/CloakedFileScanOperation.cs @@ -3,13 +3,18 @@ using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; +using Microsoft.Extensions.Logging; #nullable enable namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal class CloakedFileScanOperation : ScanOperationBase { - public CloakedFileScanOperation(CloakedFileScanOptions options) : base(options) + public CloakedFileScanOperation( + CloakedFileScanOptions options, + IVmrScanner vmrScanner, + ILogger logger) + : base(options, vmrScanner, logger) { } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/CodeFlowOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/CodeFlowOperation.cs index 7c5edeeafa..54c7919a7d 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/CodeFlowOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/CodeFlowOperation.cs @@ -9,19 +9,24 @@ using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.Helpers; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; #nullable enable namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal abstract class CodeFlowOperation : VmrOperationBase { - private readonly CodeFlowCommandLineOptions _options; + private readonly ICodeFlowCommandLineOptions _options; + private readonly IVmrInfo _vmrInfo; - protected CodeFlowOperation(CodeFlowCommandLineOptions options) - : base(options) + protected CodeFlowOperation( + ICodeFlowCommandLineOptions options, + IVmrInfo vmrInfo, + ILogger logger) + : base(options, logger) { _options = options; + _vmrInfo = vmrInfo; } protected override async Task ExecuteInternalAsync( @@ -41,8 +46,7 @@ protected override async Task ExecuteInternalAsync( if (_options.RepositoryDirectory is not null) { - var vmrInfo = Provider.GetRequiredService(); - vmrInfo.TmpPath = new NativePath(_options.RepositoryDirectory); + _vmrInfo.TmpPath = new NativePath(_options.RepositoryDirectory); } if (_options.Build.HasValue && _options.Commit != null) diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/ForwardFlowOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/ForwardFlowOperation.cs index 0892f16bdf..314e9ff5d1 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/ForwardFlowOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/ForwardFlowOperation.cs @@ -6,13 +6,17 @@ using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.Helpers; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; #nullable enable namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; -internal class ForwardFlowOperation(ForwardFlowCommandLineOptions options) - : CodeFlowOperation(options) +internal class ForwardFlowOperation( + ForwardFlowCommandLineOptions options, + IVmrForwardFlower vmrForwardFlower, + IVmrInfo vmrInfo, + ILogger logger) + : CodeFlowOperation(options, vmrInfo, logger) { private readonly ForwardFlowCommandLineOptions _options = options; @@ -21,8 +25,7 @@ protected override async Task FlowAsync( NativePath targetDirectory, string? shaToFlow, CancellationToken cancellationToken) - => await Provider.GetRequiredService() - .FlowForwardAsync( + => await vmrForwardFlower.FlowForwardAsync( mappingName, new NativePath(targetDirectory), shaToFlow, diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/GenerateTpnOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/GenerateTpnOperation.cs index 9c7b02dd08..5e15911834 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/GenerateTpnOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/GenerateTpnOperation.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; #nullable enable namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; @@ -12,20 +11,23 @@ namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal class GenerateTpnOperation : Operation { private readonly GenerateTpnCommandLineOptions _options; + private readonly IThirdPartyNoticesGenerator _generator; + private readonly IVmrDependencyTracker _dependencyTracker; - public GenerateTpnOperation(GenerateTpnCommandLineOptions options) - : base(options, options.RegisterServices()) + public GenerateTpnOperation( + GenerateTpnCommandLineOptions options, + IThirdPartyNoticesGenerator generator, + IVmrDependencyTracker dependencyTracker) { _options = options; + _generator = generator; + _dependencyTracker = dependencyTracker; } public override async Task ExecuteAsync() { - var generator = Provider.GetRequiredService(); - var dependencyTracker = Provider.GetRequiredService(); - await dependencyTracker.InitializeSourceMappings(); - - await generator.UpdateThirdPartyNotices(_options.TpnTemplate); + await _dependencyTracker.InitializeSourceMappings(); + await _generator.UpdateThirdPartyNotices(_options.TpnTemplate); return 0; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/GetRepoVersionOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/GetRepoVersionOperation.cs index 4d8fc7b0b9..78b854ac4f 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/GetRepoVersionOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/GetRepoVersionOperation.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; #nullable enable @@ -15,11 +14,20 @@ namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal class GetRepoVersionOperation : Operation { private readonly GetRepoVersionCommandLineOptions _options; - - public GetRepoVersionOperation(GetRepoVersionCommandLineOptions options) - : base(options, options.RegisterServices()) + private readonly IVmrDependencyTracker _dependencyTracker; + private readonly IVmrRepoVersionResolver _vmrManager; + private readonly ILogger _logger; + + public GetRepoVersionOperation( + GetRepoVersionCommandLineOptions options, + IVmrDependencyTracker dependencyTracker, + IVmrRepoVersionResolver vmrManager, + ILogger logger) { _options = options; + _dependencyTracker = dependencyTracker; + _vmrManager = vmrManager; + _logger = logger; } public override async Task ExecuteAsync() @@ -29,25 +37,23 @@ public override async Task ExecuteAsync() // If there are no repositories, list all. if (!repositories.Any()) { - var dependencyTracker = Provider.GetRequiredService(); - await dependencyTracker.InitializeSourceMappings(); + await _dependencyTracker.InitializeSourceMappings(); - repositories = dependencyTracker.Mappings.Select(m => m.Name).ToList(); + repositories = _dependencyTracker.Mappings.Select(m => m.Name).ToList(); } if (!repositories.Any()) { - Logger.LogError("No repositories found in the VMR."); + _logger.LogError("No repositories found in the VMR."); return Constants.ErrorCode; } - var vmrManager = Provider.GetRequiredService(); var maxRepoNameLength = repositories.Max(r => r.Length); foreach (var repo in repositories) { var paddedRepoName = repo.PadRight(maxRepoNameLength); - Console.WriteLine($"{paddedRepoName} {await vmrManager.GetVersion(repo)}"); + Console.WriteLine($"{paddedRepoName} {await _vmrManager.GetVersion(repo)}"); } return 0; diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/InitializeOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/InitializeOperation.cs index 135fc8f0dd..d281d7ee9f 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/InitializeOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/InitializeOperation.cs @@ -7,7 +7,7 @@ using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.Helpers; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; #nullable enable namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; @@ -15,11 +15,16 @@ namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal class InitializeOperation : VmrOperationBase { private readonly InitializeCommandLineOptions _options; + private readonly IVmrInitializer _vmrInitializer; - public InitializeOperation(InitializeCommandLineOptions options) - : base(options) + public InitializeOperation( + InitializeCommandLineOptions options, + IVmrInitializer vmrInitializer, + ILogger logger) + : base(options, logger) { _options = options; + _vmrInitializer = vmrInitializer; } protected override async Task ExecuteInternalAsync( @@ -27,18 +32,17 @@ protected override async Task ExecuteInternalAsync( string? targetRevision, IReadOnlyCollection additionalRemotes, CancellationToken cancellationToken) - => await Provider.GetRequiredService() - .InitializeRepository( - repoName, - targetRevision, - null, - _options.Recursive, - new NativePath(_options.SourceMappings), - additionalRemotes, - _options.ComponentTemplate, - _options.TpnTemplate, - _options.GenerateCodeowners, - _options.GenerateCredScanSuppressions, - _options.DiscardPatches, - cancellationToken); + => await _vmrInitializer.InitializeRepository( + repoName, + targetRevision, + null, + _options.Recursive, + new NativePath(_options.SourceMappings), + additionalRemotes, + _options.ComponentTemplate, + _options.TpnTemplate, + _options.GenerateCodeowners, + _options.GenerateCredScanSuppressions, + _options.DiscardPatches, + cancellationToken); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/PushOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/PushOperation.cs index a34571663f..4747ce75ef 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/PushOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/PushOperation.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; #nullable enable @@ -14,36 +13,41 @@ namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal class PushOperation : Operation { private readonly VmrPushCommandLineOptions _options; + private readonly IVmrPusher _vmrPusher; + private readonly ILogger _logger; - public PushOperation(VmrPushCommandLineOptions options) - : base(options, options.RegisterServices()) + public PushOperation( + VmrPushCommandLineOptions options, + IVmrPusher vmrPusher, + ILogger logger) { _options = options; + _vmrPusher = vmrPusher; + _logger = logger; } public override async Task ExecuteAsync() { - var vmrPusher = Provider.GetRequiredService(); - using var listener = CancellationKeyListener.ListenForCancellation(Logger); - + using var listener = CancellationKeyListener.ListenForCancellation(_logger); + if (!_options.SkipCommitVerification && _options.CommitVerificationPat == null) { - Logger.LogError("Please use --commit-verification-pat to specify a GitHub token with basic scope to be used for authenticating to GitHub GraphQL API"); + _logger.LogError("Please use --commit-verification-pat to specify a GitHub token with basic scope to be used for authenticating to GitHub GraphQL API"); return Constants.ErrorCode; } - + try { - await vmrPusher.Push(_options.RemoteUrl, _options.Branch, _options.SkipCommitVerification, _options.CommitVerificationPat, listener.Token); + await _vmrPusher.Push(_options.RemoteUrl, _options.Branch, _options.SkipCommitVerification, _options.CommitVerificationPat, listener.Token); return 0; } - catch(Exception e) + catch (Exception e) { - Logger.LogError( + _logger.LogError( "Pushing to the VMR failed. {exception}", Environment.NewLine + e.Message); - Logger.LogDebug("{exception}", e); + _logger.LogDebug("{exception}", e); return Constants.ErrorCode; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/ScanOperationBase.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/ScanOperationBase.cs index 06cc534064..c74255135c 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/ScanOperationBase.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/ScanOperationBase.cs @@ -1,31 +1,37 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; -using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; +using Microsoft.DotNet.DarcLib.VirtualMonoRepo; +using Microsoft.Extensions.Logging; #nullable enable namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal abstract class ScanOperationBase : Operation where T : IVmrScanner { - private readonly VmrScanOptions _options; - - public ScanOperationBase(VmrScanOptions options) : base(options, options.RegisterServices()) + private readonly IVmrScanOptions _options; + private readonly IVmrScanner _vmrScanner; + private readonly ILogger> _logger; + + public ScanOperationBase( + IVmrScanOptions options, + IVmrScanner vmrScanner, + ILogger> logger) { _options = options; + _vmrScanner = vmrScanner; + _logger = logger; } public override async Task ExecuteAsync() { - var vmrScanner = Provider.GetRequiredService(); - using var listener = CancellationKeyListener.ListenForCancellation(Logger); + using var listener = CancellationKeyListener.ListenForCancellation(_logger); - var files = await vmrScanner.ScanVmr(_options.BaselineFilePath, listener.Token); + var files = await _vmrScanner.ScanVmr(_options.BaselineFilePath, listener.Token); foreach (var file in files) { diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/UpdateOperation.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/UpdateOperation.cs index d1004fb698..709b5cc0a1 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/UpdateOperation.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/UpdateOperation.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; #nullable enable namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; @@ -14,11 +14,16 @@ namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal class UpdateOperation : VmrOperationBase { private readonly UpdateCommandLineOptions _options; + private readonly IVmrUpdater _vmrUpdater; - public UpdateOperation(UpdateCommandLineOptions options) - : base(options) + public UpdateOperation( + UpdateCommandLineOptions options, + IVmrUpdater vmrUpdater, + ILogger logger) + : base(options, logger) { _options = options; + _vmrUpdater = vmrUpdater; } protected override async Task ExecuteInternalAsync( @@ -26,8 +31,7 @@ protected override async Task ExecuteInternalAsync( string? targetRevision, IReadOnlyCollection additionalRemotes, CancellationToken cancellationToken) - => await Provider.GetRequiredService() - .UpdateRepository( + => await _vmrUpdater.UpdateRepository( repoName, targetRevision, targetVersion: null, diff --git a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/VmrOperationBase.cs b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/VmrOperationBase.cs index fc76375962..6d324df48d 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/VmrOperationBase.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Operations/VirtualMonoRepo/VmrOperationBase.cs @@ -17,11 +17,14 @@ namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; internal abstract class VmrOperationBase : Operation { private readonly IBaseVmrCommandLineOptions _options; + private readonly ILogger _logger; - protected VmrOperationBase(IBaseVmrCommandLineOptions options) - : base(options, options.RegisterServices()) + protected VmrOperationBase( + IBaseVmrCommandLineOptions options, + ILogger logger) { _options = options; + _logger = logger; } /// @@ -33,7 +36,7 @@ public override async Task ExecuteAsync() if (!repositories.Any()) { - Logger.LogError("Please specify at least one repository to synchronize"); + _logger.LogError("Please specify at least one repository to synchronize"); return Constants.ErrorCode; } @@ -58,7 +61,7 @@ public override async Task ExecuteAsync() // We have a graceful cancellation to not leave the git repo in some inconsistent state // This is mainly useful for manual use but can be also useful in CI when we time out but still want to push what we committed - using var listener = CancellationKeyListener.ListenForCancellation(Logger); + using var listener = CancellationKeyListener.ListenForCancellation(_logger); try { @@ -93,7 +96,7 @@ private async Task ExecuteAsync( IReadOnlyCollection additionalRemotes, CancellationToken cancellationToken) { - using (Logger.BeginScope(repoName)) + using (_logger.BeginScope(repoName)) { try { @@ -102,17 +105,17 @@ private async Task ExecuteAsync( } catch (EmptySyncException e) { - Logger.LogInformation("{message}", e.Message); + _logger.LogInformation("{message}", e.Message); return true; } catch (Exception e) { - Logger.LogError( + _logger.LogError( "Failed to synchronize repo {name}{exception}.", repoName, Environment.NewLine + e.Message); - Logger.LogDebug("{exception}", e); + _logger.LogDebug("{exception}", e); return false; } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/AddBuildToChannelCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/AddBuildToChannelCommandLineOptions.cs index 959bc1569f..a213fa0178 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/AddBuildToChannelCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/AddBuildToChannelCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("add-build-to-channel", HelpText = "Add a build to a channel.")] -internal class AddBuildToChannelCommandLineOptions : CommandLineOptions +internal class AddBuildToChannelCommandLineOptions : CommandLineOptions { [Option("id", Required = true, HelpText = "BAR id of build to assign to channel.")] [RedactFromLogging] @@ -66,9 +66,4 @@ internal class AddBuildToChannelCommandLineOptions : CommandLineOptions [Option("no-wait", HelpText = "If set, Darc won't wait for the asset publishing and channel assignment. The operation continues asynchronously in AzDO.")] public bool NoWait { get; set; } - - public override Operation GetOperation() - { - return new AddBuildToChannelOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/AddChannelCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/AddChannelCommandLineOptions.cs index b14a4bfeed..4339a2979d 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/AddChannelCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/AddChannelCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("add-channel", HelpText = "Creates a new channel.")] -internal class AddChannelCommandLineOptions : CommandLineOptions +internal class AddChannelCommandLineOptions : CommandLineOptions { [Option('n', "name", Required = true, HelpText = "Name of channel to create.")] public string Name { get; set; } @@ -18,8 +18,10 @@ internal class AddChannelCommandLineOptions : CommandLineOptions [Option('i', "internal", HelpText = "Channel is internal only. This option is currently non-functional")] public bool Internal { get; set; } - public override Operation GetOperation() - { - return new AddChannelOperation(this); - } + public override bool IsOutputFormatSupported() + => OutputFormat switch + { + DarcOutputType.json => true, + _ => base.IsOutputFormatSupported(), + }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/AddDefaultChannelCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/AddDefaultChannelCommandLineOptions.cs index 97dc18f346..559d68fd95 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/AddDefaultChannelCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/AddDefaultChannelCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("add-default-channel", HelpText = "Add a channel that a build of a branch+repository is automatically applied to.")] -internal class AddDefaultChannelCommandLineOptions : CommandLineOptions +internal class AddDefaultChannelCommandLineOptions : CommandLineOptions { [Option("channel", Required = true, HelpText = "Name of channel that a build of 'branch' and 'repo' should be applied to.")] public string Channel { get; set; } @@ -23,9 +23,4 @@ internal class AddDefaultChannelCommandLineOptions : CommandLineOptions [Option('q', "quiet", HelpText = "Do not prompt if the target repository/branch does not exist.")] public bool NoConfirmation { get; set; } - - public override Operation GetOperation() - { - return new AddDefaultChannelOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/AddDependencyCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/AddDependencyCommandLineOptions.cs index 476793f92c..58a1fa026f 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/AddDependencyCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/AddDependencyCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("add-dependency", HelpText = "Add a new dependency to version files.")] -internal class AddDependencyCommandLineOptions : CommandLineOptions +internal class AddDependencyCommandLineOptions : CommandLineOptions { [Option('n', "name", Required = true, HelpText = "Name of dependency to add.")] public string Name { get; set; } @@ -30,9 +30,4 @@ internal class AddDependencyCommandLineOptions : CommandLineOptions [Option("coherent-parent", HelpText = "Restrict updates to this dependency based on version of a dependency from another repo. " + "See https://github.com/dotnet/arcade/blob/main/Documentation/DependencyDescriptionFormat.md#dependency-description-overview for more information.")] public string CoherentParentDependencyName { get; set; } - - public override Operation GetOperation() - { - return new AddDependencyOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/AddSubscriptionCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/AddSubscriptionCommandLineOptions.cs index 0e999c3c16..68a536a01d 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/AddSubscriptionCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/AddSubscriptionCommandLineOptions.cs @@ -1,14 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using CommandLine; using Microsoft.DotNet.Darc.Operations; -using System.Collections.Generic; namespace Microsoft.DotNet.Darc.Options; [Verb("add-subscription", HelpText = "Add a new subscription.")] -internal class AddSubscriptionCommandLineOptions : SubscriptionCommandLineOptions +internal class AddSubscriptionCommandLineOptions : SubscriptionCommandLineOptions { [Option("channel", HelpText = "Name of channel to pull from.")] public string Channel { get; set; } @@ -54,14 +54,9 @@ internal class AddSubscriptionCommandLineOptions : SubscriptionCommandLineOption [Option("no-trigger", SetName = "notrigger", HelpText = "Do not trigger the subscription on creation.")] public bool NoTriggerOnCreate { get; set; } - [Option("validate-coherency", HelpText="PR is not merged if the coherency algorithm failed.")] + [Option("validate-coherency", HelpText = "PR is not merged if the coherency algorithm failed.")] public bool ValidateCoherencyCheckMergePolicy { get; set; } [Option("source-enabled", HelpText = "Get only source-enabled (VMR code flow) subscriptions.", Default = false)] public bool SourceEnabled { get; set; } - - public override Operation GetOperation() - { - return new AddSubscriptionOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/AuthenticateCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/AuthenticateCommandLineOptions.cs index 048858dbc4..94c5785030 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/AuthenticateCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/AuthenticateCommandLineOptions.cs @@ -7,13 +7,9 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("authenticate", HelpText = "Stores the VSTS and GitHub tokens required for remote operations.")] -internal class AuthenticateCommandLineOptions : CommandLineOptions +internal class AuthenticateCommandLineOptions : CommandLineOptions { [Option("clear", HelpText = "Clear any settings to defaults.")] public bool Clear { get; set; } - public override Operation GetOperation() - { - return new AuthenticateOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/CloneCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/CloneCommandLineOptions.cs index 1e9721b6c7..74d3db95a9 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/CloneCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/CloneCommandLineOptions.cs @@ -1,15 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using CommandLine; using Microsoft.DotNet.Darc.Operations; -using System.Collections.Generic; namespace Microsoft.DotNet.Darc.Options; [Verb("clone", HelpText = "Clone a remote repo and all of its dependency repos")] -internal class CloneCommandLineOptions : CommandLineOptions +internal class CloneCommandLineOptions : CommandLineOptions { [Option("repo", HelpText = "Remote repository to start the clone operation at. If none specified, clone all that the current repo depends on.")] public string RepoUri { get; set; } @@ -33,9 +33,4 @@ internal class CloneCommandLineOptions : CommandLineOptions [Option('d', "depth", Default = uint.MaxValue, HelpText = "Depth to clone the repos to. Defaults to infinite.")] public uint CloneDepth { get; set; } - - public override Operation GetOperation() - { - return new CloneOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/CommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/CommandLineOptions.cs index c8bd4b10c7..02600260d5 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/CommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/CommandLineOptions.cs @@ -1,16 +1,31 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.IO; using CommandLine; using Maestro.Common; using Maestro.Common.AzureDevOpsTokens; +using Microsoft.Arcade.Common; using Microsoft.DotNet.Darc.Helpers; using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.DarcLib; +using Microsoft.DotNet.DarcLib.Helpers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; namespace Microsoft.DotNet.Darc.Options; +public abstract class CommandLineOptions : CommandLineOptions where T : Operation +{ + public override Operation GetOperation(ServiceProvider sp) + { + return ActivatorUtilities.CreateInstance(sp, this); + } +} + public abstract class CommandLineOptions : ICommandLineOptions { [Option('p', "password", @@ -46,7 +61,22 @@ public abstract class CommandLineOptions : ICommandLineOptions [Option("output-format", Default = DarcOutputType.text, HelpText = "Desired output type of darc. Valid values are 'json' and 'text'. Case sensitive.")] - public DarcOutputType OutputFormat { get; set; } + public DarcOutputType OutputFormat { + get + { + return _outputFormat; + } + set + { + _outputFormat = value; + if (!IsOutputFormatSupported()) + { + throw new ArgumentException($"Output format {_outputFormat} is not supported by operation ${this.GetType().Name}"); + } + } + } + + private DarcOutputType _outputFormat; /// /// Designates that darc is run from a CI environment. @@ -55,7 +85,7 @@ public abstract class CommandLineOptions : ICommandLineOptions [Option("ci", HelpText = "Designates that darc is run from a CI environment with some features disabled (e.g. interactive browser sign-in to Maestro)")] public bool IsCi { get; set; } - public abstract Operation GetOperation(); + public abstract Operation GetOperation(ServiceProvider sp); public IRemoteTokenProvider GetRemoteTokenProvider() => new RemoteTokenProvider(GetAzdoTokenProvider(), GetGitHubTokenProvider()); @@ -83,4 +113,68 @@ public void InitializeFromSettings(ILogger logger) BuildAssetRegistryBaseUri ??= localSettings.BuildAssetRegistryBaseUri; BuildAssetRegistryToken ??= localSettings.BuildAssetRegistryToken; } + + /// + /// Indicates whether the requested output format is supported. + /// + public virtual bool IsOutputFormatSupported() + => OutputFormat switch + { + DarcOutputType.text => true, + _ => false + }; + + public virtual IServiceCollection RegisterServices(IServiceCollection services) + { + // Because the internal logging in DarcLib tends to be chatty and non-useful, + // we remap the --verbose switch onto 'info', --debug onto highest level, and the + // default level onto warning + LogLevel level = LogLevel.Warning; + if (Debug) + { + level = LogLevel.Debug; + } + else if (Verbose) + { + level = LogLevel.Information; + } + + services ??= new ServiceCollection(); + services.AddLogging(b => b + .AddConsole(o => o.FormatterName = CompactConsoleLoggerFormatter.FormatterName) + .AddConsoleFormatter() + .SetMinimumLevel(level)); + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddTransient(sp => new ProcessManager(sp.GetRequiredService>(), GitLocation)); + services.TryAddSingleton(sp => RemoteFactory.GetBarClient(this, sp.GetRequiredService>())); + services.TryAddSingleton(sp => sp.GetRequiredService()); + services.TryAddTransient(sp => sp.GetRequiredService>()); + services.TryAddTransient(); + services.TryAddTransient(sp => ActivatorUtilities.CreateInstance(sp, Path.GetTempPath())); + services.Configure(o => + { + o["default"] = new AzureDevOpsCredentialResolverOptions + { + Token = AzureDevOpsPat, + FederatedToken = FederatedToken, + DisableInteractiveAuth = IsCi, + }; + }); + services.TryAddSingleton(); + services.TryAddSingleton(s => + new AzureDevOpsClient( + s.GetRequiredService(), + s.GetRequiredService(), + s.GetRequiredService()) + ); + services.TryAddSingleton(s => + s.GetRequiredService() + ); + services.TryAddSingleton(_ => new RemoteTokenProvider(AzureDevOpsPat, GitHubPat)); + services.TryAddSingleton(_ => this); + + return services; + } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/DefaultChannelStatusCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/DefaultChannelStatusCommandLineOptions.cs index cd5d7e19be..6d43b3d932 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/DefaultChannelStatusCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/DefaultChannelStatusCommandLineOptions.cs @@ -7,16 +7,11 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("default-channel-status", HelpText = "Enables or disables a default channel association")] -internal class DefaultChannelStatusCommandLineOptions : UpdateDefaultChannelBaseCommandLineOptions +internal class DefaultChannelStatusCommandLineOptions : UpdateDefaultChannelBaseCommandLineOptions { [Option('e', "enable", HelpText = "Enable default channel.")] public bool Enable { get; set; } [Option('d', "disable", HelpText = "Disable default channel.")] public bool Disable { get; set; } - - public override Operation GetOperation() - { - return new DefaultChannelStatusOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteBuildFromChannelCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteBuildFromChannelCommandLineOptions.cs index d33035f9d6..79752de326 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteBuildFromChannelCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteBuildFromChannelCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("delete-build-from-channel", HelpText = "Removes a build from a channel.")] -internal class DeleteBuildFromChannelCommandLineOptions : CommandLineOptions +internal class DeleteBuildFromChannelCommandLineOptions : CommandLineOptions { [Option("id", Required = true, HelpText = "BAR id of build to assign to channel.")] [RedactFromLogging] @@ -16,8 +16,4 @@ internal class DeleteBuildFromChannelCommandLineOptions : CommandLineOptions [Option("channel", Required = true, HelpText = "Channel to remove the build from.")] public string Channel { get; set; } - public override Operation GetOperation() - { - return new DeleteBuildFromChannelOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteChannelCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteChannelCommandLineOptions.cs index a0b7d45bb7..0456ddf091 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteChannelCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteChannelCommandLineOptions.cs @@ -7,13 +7,9 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("delete-channel", HelpText = "Deletes an existing channel.")] -internal class DeleteChannelCommandLineOptions : CommandLineOptions +internal class DeleteChannelCommandLineOptions : CommandLineOptions { [Option('n', "name", Required = true, HelpText = "Name of channel to delete.")] public string Name { get; set; } - public override Operation GetOperation() - { - return new DeleteChannelOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteDefaultChannelCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteDefaultChannelCommandLineOptions.cs index b3f8d29b1a..e500503c13 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteDefaultChannelCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteDefaultChannelCommandLineOptions.cs @@ -3,14 +3,15 @@ using CommandLine; using Microsoft.DotNet.Darc.Operations; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.Darc.Options; [Verb("delete-default-channel", HelpText = "Remove a default channel association.")] internal class DeleteDefaultChannelCommandLineOptions : DefaultChannelStatusCommandLineOptions { - public override Operation GetOperation() + public override DeleteDefaultChannelOperation GetOperation(ServiceProvider sp) { - return new DeleteDefaultChannelOperation(this); + return ActivatorUtilities.CreateInstance(sp, this); } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteSubscriptionCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteSubscriptionCommandLineOptions.cs deleted file mode 100644 index a6b2bb78ae..0000000000 --- a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteSubscriptionCommandLineOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using CommandLine; -using Microsoft.DotNet.Darc.Operations; - -namespace Microsoft.DotNet.Darc.Options; - -[Verb("delete-subscription", Hidden = true, HelpText = "Please use delete-subscriptions.")] -internal class DeleteSubscriptionCommandLineOptions : CommandLineOptions -{ - [Option('i', "id", Required = true, HelpText = "ID of subscription to delete.")] - public string Id { get; set; } - - public override Operation GetOperation() - { - return new DeleteSubscriptionOperation(this); - } -} diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteSubscriptionsCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteSubscriptionsCommandLineOptions.cs index 6ce5809574..e5b44bb706 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/DeleteSubscriptionsCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/DeleteSubscriptionsCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("delete-subscriptions", HelpText = "Delete a subscription or set of subscriptions matching criteria.")] -internal class DeleteSubscriptionsCommandLineOptions : SubscriptionsCommandLineOptions +internal class DeleteSubscriptionsCommandLineOptions : SubscriptionsCommandLineOptions { [Option("id", HelpText = "Delete a specific subscription by id.")] public string Id { get; set; } @@ -15,8 +15,4 @@ internal class DeleteSubscriptionsCommandLineOptions : SubscriptionsCommandLineO [Option('q', "quiet", HelpText = "Do not confirm which subscriptions are about to be triggered.")] public bool NoConfirmation { get; set; } - public override Operation GetOperation() - { - return new DeleteSubscriptionsOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GatherDropCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GatherDropCommandLineOptions.cs index 6ee1d61ec9..c16b824471 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GatherDropCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GatherDropCommandLineOptions.cs @@ -1,14 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using CommandLine; using Microsoft.DotNet.Darc.Operations; -using System.Collections.Generic; namespace Microsoft.DotNet.Darc.Options; [Verb("gather-drop", HelpText = "Gather a drop of the outputs for a build")] -internal class GatherDropCommandLineOptions : CommandLineOptions +internal class GatherDropCommandLineOptions : CommandLineOptions { [Option('i', "id", Separator = ',', HelpText = "BAR ID(s) of the root build(s) that we want to gather. comma separated.")] [RedactFromLogging] @@ -21,11 +21,11 @@ internal class GatherDropCommandLineOptions : CommandLineOptions [RedactFromLogging] public string Commit { get; set; } - [Option('o',"output-dir", Required = true, HelpText = "Output directory to place build drop.")] + [Option('o', "output-dir", Required = true, HelpText = "Output directory to place build drop.")] [RedactFromLogging] public string OutputDirectory { get; set; } - [Option("use-relative-paths", Default = false, HelpText = "If true, make all paths in the resultant manifest relative to the value of output-dir")] + [Option("use-relative-paths", Default = false, HelpText = "If true, make all paths in the resultant manifest relative to the value of output-dir")] public bool UseRelativePathsInManifest { get; set; } [Option("max-downloads", Default = 4, HelpText = "Maximum concurrent downloads.")] @@ -37,7 +37,7 @@ internal class GatherDropCommandLineOptions : CommandLineOptions [Option('f', "full", HelpText = "Gather the full drop (build and all input builds).")] public bool Transitive { get; set; } - [Option("release-name", Default ="3.0.0-previewN", HelpText = "Name of release to use when generating release json.")] + [Option("release-name", Default = "3.0.0-previewN", HelpText = "Name of release to use when generating release json.")] public string ReleaseName { get; set; } [Option("continue-on-error", HelpText = "Continue on error rather than halting.")] @@ -86,9 +86,4 @@ internal class GatherDropCommandLineOptions : CommandLineOptions [Option("asset-filter", HelpText = "Only download assets matching the given regex filter")] public string AssetFilter { get; set; } - - public override Operation GetOperation() - { - return new GatherDropOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetAssetCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetAssetCommandLineOptions.cs index 697d758626..212cf77801 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetAssetCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetAssetCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-asset", HelpText = "Get information about an asset.")] -internal class GetAssetCommandLineOptions : CommandLineOptions +internal class GetAssetCommandLineOptions : CommandLineOptions { [Option("name", Required = false, HelpText = "Name of asset to look up")] public string Name { get; set; } @@ -24,8 +24,10 @@ internal class GetAssetCommandLineOptions : CommandLineOptions [Option("max-age", Default = 30, HelpText = "Show builds with a max age of this many days.")] public int MaxAgeInDays { get; set; } - public override Operation GetOperation() - { - return new GetAssetOperation(this); - } + public override bool IsOutputFormatSupported() + => OutputFormat switch + { + DarcOutputType.json => true, + _ => base.IsOutputFormatSupported(), + }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetBuildCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetBuildCommandLineOptions.cs index 763ed390c5..cf9d66ed41 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetBuildCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetBuildCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-build", HelpText = "Retrieves a specific build of a repository")] -internal class GetBuildCommandLineOptions : CommandLineOptions +internal class GetBuildCommandLineOptions : CommandLineOptions { [Option("id", HelpText = "Build id.")] [RedactFromLogging] @@ -23,8 +23,10 @@ internal class GetBuildCommandLineOptions : CommandLineOptions [Option("extended", HelpText = "Show all available fields (applies to JSON output-format only)")] public bool ExtendedDetails { get; set; } - public override Operation GetOperation() - { - return new GetBuildOperation(this); - } + public override bool IsOutputFormatSupported() + => OutputFormat switch + { + DarcOutputType.json => true, + _ => base.IsOutputFormatSupported(), + }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetChannelCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetChannelCommandLineOptions.cs index 48557a16df..a0ba447eb5 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetChannelCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetChannelCommandLineOptions.cs @@ -7,13 +7,8 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-channel", HelpText = "Get a specific channel.")] -internal class GetChannelCommandLineOptions : CommandLineOptions +internal class GetChannelCommandLineOptions : CommandLineOptions { [Option("id", Required = true, HelpText = "ID of the channel to show.")] public int Id { get; set; } - - public override Operation GetOperation() - { - return new GetChannelOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetChannelsCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetChannelsCommandLineOptions.cs index 3d40bf978b..10ae341c2c 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetChannelsCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetChannelsCommandLineOptions.cs @@ -7,10 +7,12 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-channels", HelpText = "Get a list of channels.")] -internal class GetChannelsCommandLineOptions : CommandLineOptions +internal class GetChannelsCommandLineOptions : CommandLineOptions { - public override Operation GetOperation() - { - return new GetChannelsOperation(this); - } + public override bool IsOutputFormatSupported() + => OutputFormat switch + { + DarcOutputType.json => true, + _ => base.IsOutputFormatSupported(), + }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetDefaultChannelsCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetDefaultChannelsCommandLineOptions.cs index abd7041ce2..1fb35e00e3 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetDefaultChannelsCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetDefaultChannelsCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-default-channels", HelpText = "Gets a list of repo+branch combinations and their associated default channels for builds.")] -internal class GetDefaultChannelsCommandLineOptions : CommandLineOptions +internal class GetDefaultChannelsCommandLineOptions : CommandLineOptions { [Option("source-repo", HelpText = "Filter by a specific source repository. Matches on substring.")] public string SourceRepository { get; set; } @@ -18,8 +18,4 @@ internal class GetDefaultChannelsCommandLineOptions : CommandLineOptions [Option("channel", HelpText = "Filter by a channel name. Matches on substring.")] public string Channel { get; set; } - public override Operation GetOperation() - { - return new GetDefaultChannelsOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetDependenciesCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetDependenciesCommandLineOptions.cs index 5f90315869..3db45a8bb7 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetDependenciesCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetDependenciesCommandLineOptions.cs @@ -7,13 +7,8 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-dependencies", HelpText = "Get local dependencies.")] -internal class GetDependenciesCommandLineOptions : CommandLineOptions +internal class GetDependenciesCommandLineOptions : CommandLineOptions { [Option('n', "name", HelpText = "Name of dependency to query for.")] public string Name { get; set; } - - public override Operation GetOperation() - { - return new GetDependenciesOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetDependencyFlowGraphCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetDependencyFlowGraphCommandLineOptions.cs index d800ae4352..f51992e4b7 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetDependencyFlowGraphCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetDependencyFlowGraphCommandLineOptions.cs @@ -1,14 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using CommandLine; using Microsoft.DotNet.Darc.Operations; -using System.Collections.Generic; namespace Microsoft.DotNet.Darc.Options; [Verb("get-flow-graph", HelpText = "Get dependency flow graph")] -internal class GetDependencyFlowGraphCommandLineOptions : CommandLineOptions +internal class GetDependencyFlowGraphCommandLineOptions : CommandLineOptions { [Option("graphviz", HelpText = @"Writes the flow graph in GraphViz (dot) form, into the specified file.")] [RedactFromLogging] @@ -16,7 +16,7 @@ internal class GetDependencyFlowGraphCommandLineOptions : CommandLineOptions [Option("include-disabled-subscriptions", HelpText = @"Include edges that have disabled subscriptions")] public bool IncludeDisabledSubscriptions { get; set; } - + [Option("frequencies", Separator = ',', Default = new string[] { "everyWeek", "twiceDaily", "everyDay", "everyBuild", "none", }, HelpText = @"Include only subscriptions with the specific update frequencies in the graph.")] public IEnumerable IncludedFrequencies { get; set; } @@ -29,9 +29,4 @@ internal class GetDependencyFlowGraphCommandLineOptions : CommandLineOptions [Option("days", Default = 7, HelpText = @"Number of Days to summarize build times over")] public int Days { get; set; } - - public override Operation GetOperation() - { - return new GetDependencyFlowGraphOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetDependencyGraphCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetDependencyGraphCommandLineOptions.cs index 1e827964cd..6cffdb6b3e 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetDependencyGraphCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetDependencyGraphCommandLineOptions.cs @@ -1,14 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using CommandLine; using Microsoft.DotNet.Darc.Operations; -using System.Collections.Generic; namespace Microsoft.DotNet.Darc.Options; [Verb("get-dependency-graph", HelpText = "Get local dependencies.")] -internal class GetDependencyGraphCommandLineOptions : CommandLineOptions +internal class GetDependencyGraphCommandLineOptions : CommandLineOptions { [Option('l', "local", HelpText = "Get the graph using only local information. Requires that repos-folder be passed.")] public bool Local { get; set; } @@ -55,8 +55,4 @@ internal class GetDependencyGraphCommandLineOptions : CommandLineOptions [Option("coherency", HelpText = "Report coherency information.")] public bool IncludeCoherency { get; set; } - public override Operation GetOperation() - { - return new GetDependencyGraphOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetGoalCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetGoalCommandLineOptions.cs index 4c7c5ef1e9..9aec30e942 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetGoalCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetGoalCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-goal", HelpText = "Gets Goal in minutes for a Definition in a Channel")] -internal class GetGoalCommandLineOptions : CommandLineOptions +internal class GetGoalCommandLineOptions : CommandLineOptions { [Option('c', "channel", Required = true, HelpText = "Name of channel Eg : .Net Core 5 Dev ")] public string Channel { get; set; } @@ -15,8 +15,4 @@ internal class GetGoalCommandLineOptions : CommandLineOptions [Option('d', "definition-id", Required = true, HelpText = "Azure DevOps Definition Id.")] public int DefinitionId { get; set; } - public override Operation GetOperation() - { - return new GetGoalOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetHealthCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetHealthCommandLineOptions.cs index b44c9b5fee..8f04b16712 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetHealthCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetHealthCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-health", HelpText = "Evaluate health")] -internal class GetHealthCommandLineOptions : CommandLineOptions +internal class GetHealthCommandLineOptions : CommandLineOptions { [Option("repo", HelpText = "Narrow health checkups by this repository.")] public string Repo { get; set; } @@ -15,8 +15,4 @@ internal class GetHealthCommandLineOptions : CommandLineOptions [Option("channel", HelpText = "Narrow health checkups by this channel.")] public string Channel { get; set; } - public override Operation GetOperation() - { - return new GetHealthOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetLatestBuildCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetLatestBuildCommandLineOptions.cs index 3a02feb159..a20666a2db 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetLatestBuildCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetLatestBuildCommandLineOptions.cs @@ -8,7 +8,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-latest-build", HelpText = "Retrieves the latest builds matching the specified criteria. " + "If more than one build matches then multiple builds are returned.")] -internal class GetLatestBuildCommandLineOptions : CommandLineOptions +internal class GetLatestBuildCommandLineOptions : CommandLineOptions { [Option("repo", Required = true, HelpText = "Name of repository to determine the latest build for. Match on substring")] public string Repo { get; set; } @@ -16,8 +16,4 @@ internal class GetLatestBuildCommandLineOptions : CommandLineOptions [Option("channel", HelpText = "Name of channel to query for the latest build on. Match on substring")] public string Channel { get; set; } - public override Operation GetOperation() - { - return new GetLatestBuildOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetRepositoryMergePoliciesCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetRepositoryMergePoliciesCommandLineOptions.cs index 37c48f5545..7451ab4df3 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetRepositoryMergePoliciesCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetRepositoryMergePoliciesCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-repository-policies", HelpText = "Retrieves information about repository merge policies.")] -internal class GetRepositoryMergePoliciesCommandLineOptions : CommandLineOptions +internal class GetRepositoryMergePoliciesCommandLineOptions : CommandLineOptions { [Option("repo", HelpText = "Name of repository to get repository merge policies for. Match on substring")] public string Repo { get; set; } @@ -18,8 +18,4 @@ internal class GetRepositoryMergePoliciesCommandLineOptions : CommandLineOptions [Option("all", HelpText = "List all repositories. Otherwise, branches not targeted by a batchable subscription are not listed.")] public bool All { get; set; } - public override Operation GetOperation() - { - return new GetRepositoryMergePoliciesOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/GetSubscriptionsCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/GetSubscriptionsCommandLineOptions.cs index c1659cc84d..ad7a9fa9d2 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/GetSubscriptionsCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/GetSubscriptionsCommandLineOptions.cs @@ -7,10 +7,12 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("get-subscriptions", HelpText = "Get information about subscriptions.")] -internal class GetSubscriptionsCommandLineOptions : SubscriptionsCommandLineOptions +internal class GetSubscriptionsCommandLineOptions : SubscriptionsCommandLineOptions { - public override Operation GetOperation() - { - return new GetSubscriptionsOperation(this); - } + public override bool IsOutputFormatSupported() + => OutputFormat switch + { + DarcOutputType.json => true, + _ => base.IsOutputFormatSupported(), + }; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/ICommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/ICommandLineOptions.cs index 697ccd6cc1..eec3ce231e 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/ICommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/ICommandLineOptions.cs @@ -4,9 +4,11 @@ using Maestro.Common; using Maestro.Common.AzureDevOpsTokens; using Microsoft.DotNet.Darc.Operations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc.Options; + public interface ICommandLineOptions { string AzureDevOpsPat { get; set; } @@ -20,7 +22,7 @@ public interface ICommandLineOptions bool Verbose { get; set; } bool IsCi { get; set; } - Operation GetOperation(); + Operation GetOperation(ServiceProvider sp); IRemoteTokenProvider GetRemoteTokenProvider(); IAzureDevOpsTokenProvider GetAzdoTokenProvider(); IRemoteTokenProvider GetGitHubTokenProvider(); diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/SetGoalCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/SetGoalCommandLineOptions.cs index 2f77fd669f..e0a26b71cb 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/SetGoalCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/SetGoalCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("set-goal", HelpText = "Creates/Updates Goal in minutes for a Definition in a Channel")] -internal class SetGoalCommandLineOptions : CommandLineOptions +internal class SetGoalCommandLineOptions : CommandLineOptions { [Option('c', "channel", Required = true, HelpText = "Name of channel Eg : .Net Core 5 Dev ")] public string Channel { get; set; } @@ -15,11 +15,6 @@ internal class SetGoalCommandLineOptions : CommandLineOptions [Option('d', "definition-id", Required = true, HelpText = "Azure DevOps Definition Id.")] public int DefinitionId { get; set; } - [Option('m', "minutes",Required = true, HelpText = "Goal time in minutes.")] + [Option('m', "minutes", Required = true, HelpText = "Goal time in minutes.")] public int Minutes { get; set; } - - public override Operation GetOperation() - { - return new SetGoalOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/SetRepositoryMergePoliciesCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/SetRepositoryMergePoliciesCommandLineOptions.cs index 732065ff1d..381535dfb5 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/SetRepositoryMergePoliciesCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/SetRepositoryMergePoliciesCommandLineOptions.cs @@ -1,14 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using CommandLine; using Microsoft.DotNet.Darc.Operations; -using System.Collections.Generic; namespace Microsoft.DotNet.Darc.Options; [Verb("set-repository-policies", HelpText = "Set merge policies for the specific repository and branch")] -internal class SetRepositoryMergePoliciesCommandLineOptions : CommandLineOptions +internal class SetRepositoryMergePoliciesCommandLineOptions : CommandLineOptions { [Option("repo", HelpText = "Name of repository to set repository merge policies for.")] public string Repository { get; set; } @@ -35,9 +35,4 @@ internal class SetRepositoryMergePoliciesCommandLineOptions : CommandLineOptions [Option('q', "quiet", HelpText = "Non-interactive mode (requires all elements to be passed on the command line).")] public bool Quiet { get; set; } - - public override Operation GetOperation() - { - return new SetRepositoryMergePoliciesOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionCommandLineOptions.cs index 6d54e1d480..c201a2d7e7 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionCommandLineOptions.cs @@ -2,10 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using CommandLine; +using Microsoft.DotNet.Darc.Operations; namespace Microsoft.DotNet.Darc.Options; -internal abstract class SubscriptionCommandLineOptions : CommandLineOptions +internal abstract class SubscriptionCommandLineOptions : CommandLineOptions where T : Operation { [Option("update-frequency", HelpText = "Frequency of updates. Valid values are: 'none', 'everyDay', 'everyBuild', 'twiceDaily', or 'everyWeek'.")] public string UpdateFrequency { get; set; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionsCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionsCommandLineOptions.cs index c90c3b4544..502d88ba31 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionsCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionsCommandLineOptions.cs @@ -1,19 +1,20 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using CommandLine; -using System.Text.RegularExpressions; using System; -using Microsoft.DotNet.Maestro.Client.Models; using System.Collections.Generic; using System.Linq; -using Microsoft.DotNet.DarcLib; +using System.Text.RegularExpressions; using System.Threading.Tasks; +using CommandLine; +using Microsoft.DotNet.Darc.Operations; +using Microsoft.DotNet.DarcLib; +using Microsoft.DotNet.Maestro.Client.Models; using Microsoft.DotNet.Services.Utility; namespace Microsoft.DotNet.Darc.Options; -internal abstract class SubscriptionsCommandLineOptions : CommandLineOptions +internal abstract class SubscriptionsCommandLineOptions : CommandLineOptions where T : Operation { [Option("target-repo", HelpText = "Filter by target repo (matches substring unless --exact or --regex is passed).")] public string TargetRepository { get; set; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionsStatusCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionsStatusCommandLineOptions.cs index 073909d721..2a294c777d 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionsStatusCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/SubscriptionsStatusCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("subscription-status", HelpText = "Enables or disables subscriptions matching a specified criteria.")] -internal class SubscriptionsStatusCommandLineOptions : SubscriptionsCommandLineOptions +internal class SubscriptionsStatusCommandLineOptions : SubscriptionsCommandLineOptions { [Option("id", HelpText = "Specific subscription's id.")] public string Id { get; set; } @@ -20,9 +20,4 @@ internal class SubscriptionsStatusCommandLineOptions : SubscriptionsCommandLineO [Option('q', "quiet", HelpText = "Do not confirm which subscriptions are about to be enabled/disabled.")] public bool NoConfirmation { get; set; } - - public override Operation GetOperation() - { - return new SubscriptionsStatusOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/TriggerSubscriptionsCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/TriggerSubscriptionsCommandLineOptions.cs index 8ecca3fbbf..1655f1e718 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/TriggerSubscriptionsCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/TriggerSubscriptionsCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("trigger-subscriptions", HelpText = "Trigger a subscription or set of subscriptions matching criteria.")] -internal class TriggerSubscriptionsCommandLineOptions : SubscriptionsCommandLineOptions +internal class TriggerSubscriptionsCommandLineOptions : SubscriptionsCommandLineOptions { [Option("id", HelpText = "Trigger a specific subscription by id.")] public string Id { get; set; } @@ -17,9 +17,4 @@ internal class TriggerSubscriptionsCommandLineOptions : SubscriptionsCommandLine [Option('q', "quiet", HelpText = "Do not confirm which subscriptions are about to be triggered.")] public bool NoConfirmation { get; set; } - - public override Operation GetOperation() - { - return new TriggerSubscriptionsOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/UpdateBuildCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/UpdateBuildCommandLineOptions.cs index 7260b5cbf2..16edd92428 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/UpdateBuildCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/UpdateBuildCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("update-build", HelpText = "Update a build with new information.")] -internal class UpdateBuildCommandLineOptions : CommandLineOptions +internal class UpdateBuildCommandLineOptions : CommandLineOptions { [Option("id", Required = true, HelpText = "Build id.")] public int Id { get; set; } @@ -17,9 +17,4 @@ internal class UpdateBuildCommandLineOptions : CommandLineOptions [Option("not-released", HelpText = "Set the build to 'not released'.")] public bool NotReleased { get; set; } - - public override Operation GetOperation() - { - return new UpdateBuildOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/UpdateDefaultChannelBaseCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/UpdateDefaultChannelBaseCommandLineOptions.cs index fe9672239f..a23b3a6265 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/UpdateDefaultChannelBaseCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/UpdateDefaultChannelBaseCommandLineOptions.cs @@ -2,10 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. using CommandLine; +using Microsoft.DotNet.Darc.Operations; namespace Microsoft.DotNet.Darc.Options; -internal abstract class UpdateDefaultChannelBaseCommandLineOptions : CommandLineOptions +internal interface IUpdateDefaultChannelBaseCommandLineOptions : ICommandLineOptions +{ + string Branch { get; set; } + string Channel { get; set; } + int Id { get; set; } + string Repository { get; set; } +} + +internal abstract class UpdateDefaultChannelBaseCommandLineOptions : CommandLineOptions, IUpdateDefaultChannelBaseCommandLineOptions where T : Operation { [Option("id", Default = -1, HelpText = "Existing default channel id")] public int Id { get; set; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/UpdateDependenciesCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/UpdateDependenciesCommandLineOptions.cs index 3472df5c65..efbcc5ef7f 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/UpdateDependenciesCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/UpdateDependenciesCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("update-dependencies", HelpText = "Update local dependencies from a channel, build or local list of packages.")] -internal class UpdateDependenciesCommandLineOptions : CommandLineOptions +internal class UpdateDependenciesCommandLineOptions : CommandLineOptions { [Option("id", HelpText = "Optional BAR id of build to be used instead of the latest build in the channel.")] [RedactFromLogging] @@ -36,9 +36,4 @@ internal class UpdateDependenciesCommandLineOptions : CommandLineOptions [Option("coherency-only", HelpText = "Only do coherency updates.")] public bool CoherencyOnly { get; set; } - - public override Operation GetOperation() - { - return new UpdateDependenciesOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/UpdateSubscriptionCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/UpdateSubscriptionCommandLineOptions.cs index 183e50870f..897b768517 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/UpdateSubscriptionCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/UpdateSubscriptionCommandLineOptions.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("update-subscription", HelpText = "Update an existing subscription. If no arguments beyond '--id' are specified, a text editor is popped up with the current values for the subscription. (As they are specified via YAML, merge policy settings must use the editor)")] -internal class UpdateSubscriptionCommandLineOptions : SubscriptionCommandLineOptions +internal class UpdateSubscriptionCommandLineOptions : SubscriptionCommandLineOptions { [Option("id", Required = true, HelpText = "Subscription's id.")] public string Id { get; set; } @@ -32,9 +32,4 @@ internal class UpdateSubscriptionCommandLineOptions : SubscriptionCommandLineOpt [Option("source-enabled", HelpText = "Get only source-enabled (VMR code flow) subscriptions.")] public bool? SourceEnabled { get; set; } - - public override Operation GetOperation() - { - return new UpdateSubscriptionOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VerifyCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VerifyCommandLineOptions.cs index 305424f63d..a814d9ba56 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VerifyCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VerifyCommandLineOptions.cs @@ -7,10 +7,6 @@ namespace Microsoft.DotNet.Darc.Options; [Verb("verify", HelpText = "Verify that the dependency information in the repository is correct.")] -internal class VerifyCommandLineOptions : CommandLineOptions +internal class VerifyCommandLineOptions : CommandLineOptions { - public override Operation GetOperation() - { - return new VerifyOperation(this); - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/BackflowCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/BackflowCommandLineOptions.cs index 812598c830..05bf5ed55f 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/BackflowCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/BackflowCommandLineOptions.cs @@ -3,18 +3,15 @@ using System.Collections.Generic; using CommandLine; -using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; [Verb("backflow", HelpText = "Flows code changes from the VMR back to target repositories.")] -internal class BackflowCommandLineOptions : CodeFlowCommandLineOptions, IBaseVmrCommandLineOptions +internal class BackflowCommandLineOptions : CodeFlowCommandLineOptions, IBaseVmrCommandLineOptions { [Value(0, Required = true, HelpText = "Repositories to backflow in the form of NAME:PATH with mapping name and local path to the target repository. " + "Path can be ommitted when --repository-dirs is supplied. " + "When no repositories passed, all repositories with changes will be synchronized.")] public override IEnumerable Repositories { get; set; } - - public override Operation GetOperation() => new BackflowOperation(this); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/CloakedFileScanOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/CloakedFileScanOptions.cs index 9aab0d3fa0..2d7f0a968a 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/CloakedFileScanOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/CloakedFileScanOptions.cs @@ -2,14 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using CommandLine; -using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; #nullable enable namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; [Verb("scan-cloaked-files", HelpText = "Scans the VMR, checking if it contains any cloaked files")] -internal class CloakedFileScanOptions : VmrScanOptions +internal class CloakedFileScanOptions : VmrScanOptions { - public override Operation GetOperation() => new CloakedFileScanOperation(this); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/CodeFlowCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/CodeFlowCommandLineOptions.cs index 0d7db20eaf..7c2692b30a 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/CodeFlowCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/CodeFlowCommandLineOptions.cs @@ -3,10 +3,20 @@ using System.Collections.Generic; using CommandLine; +using Microsoft.DotNet.Darc.Operations; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; -internal abstract class CodeFlowCommandLineOptions : VmrCommandLineOptions, IBaseVmrCommandLineOptions +internal interface ICodeFlowCommandLineOptions : IBaseVmrCommandLineOptions +{ + string BranchName { get; set; } + int? Build { get; set; } + string Commit { get; set; } + bool DiscardPatches { get; set; } + string RepositoryDirectory { get; set; } +} + +internal abstract class CodeFlowCommandLineOptions : VmrCommandLineOptions, IBaseVmrCommandLineOptions, ICodeFlowCommandLineOptions where T : Operation { public abstract IEnumerable Repositories { get; set; } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/ForwardFlowCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/ForwardFlowCommandLineOptions.cs index 2578bc618a..b8e59f0c54 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/ForwardFlowCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/ForwardFlowCommandLineOptions.cs @@ -3,18 +3,15 @@ using System.Collections.Generic; using CommandLine; -using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; [Verb("forwardflow", HelpText = "Flows code changes from a target repository to the VMR.")] -internal class ForwardFlowCommandLineOptions : CodeFlowCommandLineOptions, IBaseVmrCommandLineOptions +internal class ForwardFlowCommandLineOptions : CodeFlowCommandLineOptions, IBaseVmrCommandLineOptions { [Value(0, Required = false, HelpText = "Repositories to flow the code from in the form of NAME:PATH with mapping name and local path to the target repository. " + "Path can be ommitted when --repository-dirs is supplied. " + "When no repositories passed, all repositories with changes will be synchronized.")] public override IEnumerable Repositories { get; set; } - - public override Operation GetOperation() => new ForwardFlowOperation(this); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/GenerateTpnCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/GenerateTpnCommandLineOptions.cs index e97273872e..d750b65e7a 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/GenerateTpnCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/GenerateTpnCommandLineOptions.cs @@ -2,17 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using CommandLine; -using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; [Verb("generate-tpn", HelpText = $"Generates a new {VmrInfo.ThirdPartyNoticesFileName}.")] -internal class GenerateTpnCommandLineOptions : VmrCommandLineOptions +internal class GenerateTpnCommandLineOptions : VmrCommandLineOptions { [Option("tpn-template", Required = true, HelpText = "Path to a header template for generating THIRD-PARTY-NOTICES file.")] public string TpnTemplate { get; set; } - - public override Operation GetOperation() => new GenerateTpnOperation(this); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/GetRepoVersionCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/GetRepoVersionCommandLineOptions.cs index be7597366f..b78203418d 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/GetRepoVersionCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/GetRepoVersionCommandLineOptions.cs @@ -4,20 +4,14 @@ using System; using System.Collections.Generic; using CommandLine; -using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; #nullable enable namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; [Verb("get-version", HelpText = "Gets the current version (a SHA) of a repository in the VMR.")] -internal class GetRepoVersionCommandLineOptions : VmrCommandLineOptionsBase +internal class GetRepoVersionCommandLineOptions : VmrCommandLineOptionsBase { [Value(0, HelpText = "Repository names (e.g. runtime) to get the versions for.")] public IEnumerable Repositories { get; set; } = Array.Empty(); - - public override Operation GetOperation() => new GetRepoVersionOperation(this); - - public IServiceCollection RegisterServices() => RegisterServices(tmpPath: null); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/IBaseVmrCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/IBaseVmrCommandLineOptions.cs index 802f3b9b23..59b60dd1b1 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/IBaseVmrCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/IBaseVmrCommandLineOptions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; @@ -15,6 +14,4 @@ internal interface IBaseVmrCommandLineOptions : ICommandLineOptions IEnumerable AdditionalRemotes { get; } IEnumerable Repositories { get; } - - IServiceCollection RegisterServices(); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/InitializeCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/InitializeCommandLineOptions.cs index bb7b05d9b4..a45b47ad45 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/InitializeCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/InitializeCommandLineOptions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using CommandLine; -using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.DarcLib.VirtualMonoRepo; @@ -10,13 +9,11 @@ namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; [Verb("initialize", HelpText = "Initializes new repo(s) that haven't been synchronized into the VMR yet.")] -internal class InitializeCommandLineOptions : VmrSyncCommandLineOptions +internal class InitializeCommandLineOptions : VmrSyncCommandLineOptions { [Option('r', "recursive", Required = false, HelpText = $"Process also dependencies (from {VersionFiles.VersionDetailsXml}) recursively.")] public bool Recursive { get; set; } = false; [Option("source-mappings", Required = true, HelpText = $"A path to the {VmrInfo.SourceMappingsFileName} file to be used for syncing.")] public string SourceMappings { get; set; } - - public override Operation GetOperation() => new InitializeOperation(this); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/UpdateCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/UpdateCommandLineOptions.cs index 1dce935c69..18d5ed6b12 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/UpdateCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/UpdateCommandLineOptions.cs @@ -2,17 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using CommandLine; -using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; using Microsoft.DotNet.DarcLib; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; [Verb("update", HelpText = "Updates given repo(s) in the VMR to match given refs.")] -internal class UpdateCommandLineOptions : VmrSyncCommandLineOptions +internal class UpdateCommandLineOptions : VmrSyncCommandLineOptions { [Option('r', "recursive", Required = false, HelpText = $"Process also dependencies (from {VersionFiles.VersionDetailsXml}) recursively.")] public bool Recursive { get; set; } = false; - - public override Operation GetOperation() => new UpdateOperation(this); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrCommandLineOptions.cs index d1a144a965..5777e64d79 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrCommandLineOptions.cs @@ -1,15 +1,50 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using CommandLine; +using Microsoft.DotNet.Darc.Helpers; +using Microsoft.DotNet.DarcLib.VirtualMonoRepo; +using Microsoft.DotNet.DarcLib; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.DotNet.Darc.Operations; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; -internal abstract class VmrCommandLineOptions : VmrCommandLineOptionsBase +internal abstract class VmrCommandLineOptions : VmrCommandLineOptionsBase where T : Operation { [Option("tmp", Required = false, HelpText = "Temporary path where intermediate files are stored (e.g. cloned repos, patch files); defaults to usual TEMP.")] public string TmpPath { get; set; } - public IServiceCollection RegisterServices() => RegisterServices(TmpPath); + public override IServiceCollection RegisterServices(IServiceCollection services) + { + string tmpPath = Path.GetFullPath(TmpPath ?? Path.GetTempPath()); + LocalSettings localDarcSettings = null; + + var gitHubToken = GitHubPat; + var azureDevOpsToken = AzureDevOpsPat; + + // Read tokens from local settings if not provided + // We silence errors because the VMR synchronization often works with public repositories where tokens are not required + if (gitHubToken == null || azureDevOpsToken == null) + { + try + { + localDarcSettings = LocalSettings.GetSettings(this, NullLogger.Instance); + } + catch (DarcException) + { + // The VMR synchronization often works with public repositories where tokens are not required + } + + gitHubToken ??= localDarcSettings?.GitHubToken; + azureDevOpsToken ??= localDarcSettings?.AzureDevOpsToken; + } + + services.AddVmrManagers(GitLocation, VmrPath, tmpPath, gitHubToken, azureDevOpsToken); + services.TryAddTransient(); + return base.RegisterServices(services); + } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrCommandLineOptionsBase.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrCommandLineOptionsBase.cs index e6a27250c1..6005938b0a 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrCommandLineOptionsBase.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrCommandLineOptionsBase.cs @@ -2,49 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.IO; using CommandLine; -using Microsoft.DotNet.Darc.Helpers; -using Microsoft.DotNet.DarcLib; -using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.DotNet.Darc.Operations; #nullable enable namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; -internal abstract class VmrCommandLineOptionsBase : CommandLineOptions +internal abstract class VmrCommandLineOptionsBase : CommandLineOptions where T : Operation { [Option("vmr", HelpText = "Path to the VMR; defaults to nearest git root above the current working directory.")] public string VmrPath { get; set; } = Environment.CurrentDirectory; - - protected IServiceCollection RegisterServices(string? tmpPath) - { - tmpPath = Path.GetFullPath(tmpPath ?? Path.GetTempPath()); - LocalSettings? localDarcSettings = null; - - var gitHubToken = GitHubPat; - var azureDevOpsToken = AzureDevOpsPat; - - // Read tokens from local settings if not provided - // We silence errors because the VMR synchronization often works with public repositories where tokens are not required - if (gitHubToken == null || azureDevOpsToken == null) - { - try - { - localDarcSettings = LocalSettings.GetSettings(this, NullLogger.Instance); - } - catch (DarcException) - { - // The VMR synchronization often works with public repositories where tokens are not required - } - - gitHubToken ??= localDarcSettings?.GitHubToken; - azureDevOpsToken ??= localDarcSettings?.AzureDevOpsToken; - } - - var services = new ServiceCollection(); - services.AddVmrManagers(GitLocation, VmrPath, tmpPath, gitHubToken, azureDevOpsToken); - return services; - } } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrPushCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrPushCommandLineOptions.cs index a14d978278..ae3f6553ca 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrPushCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrPushCommandLineOptions.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using CommandLine; -using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; [Verb("push", HelpText = "Pushes given VMR branch to a given remote. Verifies public availability of pushed commits.")] -internal class VmrPushCommandLineOptions : VmrCommandLineOptions +internal class VmrPushCommandLineOptions : VmrCommandLineOptions { [Option("remote-url", Required = true, HelpText = "URL to push to")] public string RemoteUrl { get; set; } @@ -21,6 +20,4 @@ internal class VmrPushCommandLineOptions : VmrCommandLineOptions [Option("commit-verification-pat", Required = false, HelpText = "Token for authenticating to GitHub GraphQL API. Needs to have only basic scope as it will be used to look for commits in public GitHub repos.")] public string CommitVerificationPat { get; set; } - - public override Operation GetOperation() => new PushOperation(this); } diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrScanOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrScanOptions.cs index 295007b2e8..57cb26e451 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrScanOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrScanOptions.cs @@ -2,11 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. using CommandLine; +using Microsoft.DotNet.Darc.Operations; #nullable enable namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; -internal abstract class VmrScanOptions : VmrCommandLineOptions +internal interface IVmrScanOptions +{ + string? BaselineFilePath { get; set; } +} + +internal abstract class VmrScanOptions : VmrCommandLineOptions, IVmrScanOptions where T : Operation { [Option("baseline-file", Required = false, HelpText = "Path to the scan baseline file (list of files ignored by the scan)")] public string? BaselineFilePath { get; set; } = null; diff --git a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrSyncCommandLineOptions.cs b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrSyncCommandLineOptions.cs index 0c2216109b..873ec08d45 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrSyncCommandLineOptions.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Options/VirtualMonoRepo/VmrSyncCommandLineOptions.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using CommandLine; +using Microsoft.DotNet.Darc.Operations; namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo; -internal abstract class VmrSyncCommandLineOptions : VmrCommandLineOptions, IBaseVmrCommandLineOptions +internal abstract class VmrSyncCommandLineOptions : VmrCommandLineOptions, IBaseVmrCommandLineOptions where T : Operation { [Option("additional-remotes", Required = false, HelpText = "List of additional remote URIs to add to mappings in the format [mapping name]:[remote URI]. " + diff --git a/src/Microsoft.DotNet.Darc/Darc/Program.cs b/src/Microsoft.DotNet.Darc/Darc/Program.cs index ead8cd1b85..72a251390f 100644 --- a/src/Microsoft.DotNet.Darc/Darc/Program.cs +++ b/src/Microsoft.DotNet.Darc/Darc/Program.cs @@ -9,6 +9,8 @@ using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Options; using Microsoft.DotNet.Darc.Options.VirtualMonoRepo; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Darc; @@ -38,7 +40,16 @@ private static int Main(string[] args) return Parser.Default.ParseArguments(args, options) .MapResult( - (CommandLineOptions opts) => RunOperation(opts), + (CommandLineOptions opts) => { + ServiceCollection services = new(); + + opts.RegisterServices(services); + + ServiceProvider provider = services.BuildServiceProvider(); + opts.InitializeFromSettings(provider.GetRequiredService()); + + return RunOperation(opts, provider); + }, (errs => 1)); } @@ -50,14 +61,13 @@ private static int Main(string[] args) /// The primary reason for this is a workaround for an issue in the logging factory which /// causes it to not dispose the logging providers on process exit. This causes missed logs, logs that end midway through /// and cause issues with the console coloring, etc. - private static int RunOperation(CommandLineOptions opts) + private static int RunOperation(CommandLineOptions opts, ServiceProvider sp) { try { - using (Operation operation = opts.GetOperation()) - { - return operation.ExecuteAsync().GetAwaiter().GetResult(); - } + Operation operation = opts.GetOperation(sp); + + return operation.ExecuteAsync().GetAwaiter().GetResult(); } catch (Exception e) { @@ -69,7 +79,7 @@ private static int RunOperation(CommandLineOptions opts) // This order will mandate the order in which the commands are displayed if typing just 'darc' // so keep these sorted. - private static Type[] GetOptions() => + public static Type[] GetOptions() => [ typeof(AddChannelCommandLineOptions), typeof(AddDependencyCommandLineOptions), @@ -82,7 +92,6 @@ private static Type[] GetOptions() => typeof(DeleteBuildFromChannelCommandLineOptions), typeof(DeleteChannelCommandLineOptions), typeof(DeleteDefaultChannelCommandLineOptions), - typeof(DeleteSubscriptionCommandLineOptions), typeof(DeleteSubscriptionsCommandLineOptions), typeof(GatherDropCommandLineOptions), typeof(GetAssetCommandLineOptions), @@ -109,7 +118,7 @@ private static Type[] GetOptions() => ]; // These are under the "vmr" subcommand - private static Type[] GetVmrOptions() => + public static Type[] GetVmrOptions() => [ typeof(InitializeCommandLineOptions), typeof(UpdateCommandLineOptions), diff --git a/src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrRegistrations.cs b/src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrRegistrations.cs index cce0398af8..53669ac7db 100644 --- a/src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrRegistrations.cs +++ b/src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrRegistrations.cs @@ -39,9 +39,9 @@ public static IServiceCollection AddVmrManagers( var azdoTokenProvider = sp.GetRequiredService(); return new RemoteTokenProvider(azdoTokenProvider, gitHubToken); }); - services.TryAddTransient(sp => ActivatorUtilities.CreateInstance(sp, gitLocation)); services.TryAddTransient(sp => sp.GetRequiredService>()); + services.TryAddTransient(sp => new ProcessManager(sp.GetRequiredService>(), gitLocation)); services.TryAddTransient(); services.TryAddTransient(sp => ActivatorUtilities.CreateInstance(sp, sp.GetRequiredService().TmpPath.ToString())); services.TryAddTransient(); diff --git a/src/ProductConstructionService/ProductConstructionService.Api/Configuration/AuthenticationConfiguration.cs b/src/ProductConstructionService/ProductConstructionService.Api/Configuration/AuthenticationConfiguration.cs index 89842c6f9c..8cb754e6b7 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/Configuration/AuthenticationConfiguration.cs +++ b/src/ProductConstructionService/ProductConstructionService.Api/Configuration/AuthenticationConfiguration.cs @@ -2,13 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using Maestro.Authentication; -using Microsoft.DotNet.Web.Authentication.GitHub; namespace ProductConstructionService.Api.Configuration; public static class AuthenticationConfiguration { - public const string GitHubAuthenticationKey = "GitHubAuthentication"; public const string EntraAuthenticationKey = "EntraAuthentication"; // The ConfigureAuthServices we're using has a parameter that tells the service which Authentication scheme to use @@ -16,24 +14,12 @@ public static class AuthenticationConfiguration // Application scheme. We always want to use the Authentication scheme, so we're setting the path to an empty string private static readonly string AuthenticationSchemeRequestPath = string.Empty; - public static void AddEndpointAuthentication(this WebApplicationBuilder builder, bool requirePolicyRole) + public static void AddEndpointAuthentication(this WebApplicationBuilder builder) { builder.Services.AddMemoryCache(); - // TODO: https://github.com/dotnet/arcade-services/issues/3351 - IConfigurationSection gitHubAuthentication = builder.Configuration.GetSection(GitHubAuthenticationKey); - - gitHubAuthentication[nameof(GitHubAuthenticationOptions.ClientId)] - = builder.Configuration.GetRequiredValue(PcsConfiguration.GitHubClientId); - gitHubAuthentication[nameof(GitHubAuthenticationOptions.ClientSecret)] - = builder.Configuration.GetRequiredValue(PcsConfiguration.GitHubClientSecret); - IConfigurationSection entraAuthentication = builder.Configuration.GetSection(EntraAuthenticationKey); - builder.Services.ConfigureAuthServices( - requirePolicyRole, - gitHubAuthentication, - AuthenticationSchemeRequestPath, - entraAuthentication); + builder.Services.ConfigureAuthServices(AuthenticationSchemeRequestPath, entraAuthentication); } } diff --git a/src/ProductConstructionService/ProductConstructionService.Api/Configuration/GitHubClientFactoryConfiguration.cs b/src/ProductConstructionService/ProductConstructionService.Api/Configuration/GitHubClientFactoryConfiguration.cs index aa9d5e04c6..13e2d9d231 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/Configuration/GitHubClientFactoryConfiguration.cs +++ b/src/ProductConstructionService/ProductConstructionService.Api/Configuration/GitHubClientFactoryConfiguration.cs @@ -8,14 +8,12 @@ namespace ProductConstructionService.Api.Configuration; public static class GitHubClientFactoryConfiguration { - public const string GitHubAgentNameKey = $"{AuthenticationConfiguration.GitHubAuthenticationKey}:UserAgentProduct"; - public static void AddGitHubClientFactory(this WebApplicationBuilder builder) { builder.Services.Configure(o => { o.ProductHeader = new Octokit.ProductHeaderValue( - builder.Configuration.GetRequiredValue(GitHubAgentNameKey), + "PCS", Assembly.GetEntryAssembly() ?.GetCustomAttribute() ?.InformationalVersion); diff --git a/src/ProductConstructionService/ProductConstructionService.Api/Configuration/PcsConfiguration.cs b/src/ProductConstructionService/ProductConstructionService.Api/Configuration/PcsConfiguration.cs index c9ef796199..be7b6ee53c 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/Configuration/PcsConfiguration.cs +++ b/src/ProductConstructionService/ProductConstructionService.Api/Configuration/PcsConfiguration.cs @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. + using Azure.Identity; +using Maestro.Common.AzureDevOpsTokens; using Microsoft.AspNetCore.HttpLogging; +using Microsoft.DotNet.Maestro.Client; +using ProductConstructionService.Api.Controllers; +using ProductConstructionService.Api.Queue; using ProductConstructionService.Api.Telemetry; using ProductConstructionService.Api.VirtualMonoRepo; -using ProductConstructionService.Api.Queue; -using ProductConstructionService.Api.Controllers; -using Microsoft.DotNet.Maestro.Client; -using Maestro.Common.AzureDevOpsTokens; namespace ProductConstructionService.Api.Configuration; @@ -17,7 +18,6 @@ internal static class PcsConfiguration public const string ManagedIdentityId = "ManagedIdentityClientId"; public const string KeyVaultName = "KeyVaultName"; public const string GitHubToken = "BotAccount-dotnet-bot-repo-PAT"; - public const string AzDOToken = "dn-bot-all-orgs-code-r"; public const string GitHubClientId = "github-oauth-id"; public const string GitHubClientSecret = "github-oauth-secret"; public const string MaestroUri = "Maestro:Uri"; @@ -50,7 +50,7 @@ public static void ConfigurePcs( bool addSwagger, Uri? keyVaultUri = null) { - if (keyVaultUri != null) + if (keyVaultUri != null) { builder.Configuration.AddAzureKeyVault(keyVaultUri, azureCredential); } @@ -111,7 +111,7 @@ public static void ConfigurePcs( if (addEndpointAuthentication) { - builder.AddEndpointAuthentication(requirePolicyRole: true); + builder.AddEndpointAuthentication(); } builder.AddServiceDefaults(); diff --git a/src/ProductConstructionService/ProductConstructionService.Api/ProductConstructionService.Api.csproj b/src/ProductConstructionService/ProductConstructionService.Api/ProductConstructionService.Api.csproj index 53c70f85c0..702a61200c 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/ProductConstructionService.Api.csproj +++ b/src/ProductConstructionService/ProductConstructionService.Api/ProductConstructionService.Api.csproj @@ -16,7 +16,6 @@ - diff --git a/src/ProductConstructionService/ProductConstructionService.Api/VirtualMonoRepo/VmrConfiguration.cs b/src/ProductConstructionService/ProductConstructionService.Api/VirtualMonoRepo/VmrConfiguration.cs index fae90b8333..5640d693a4 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/VirtualMonoRepo/VmrConfiguration.cs +++ b/src/ProductConstructionService/ProductConstructionService.Api/VirtualMonoRepo/VmrConfiguration.cs @@ -22,7 +22,7 @@ public static void AddVmrRegistrations(this WebApplicationBuilder builder, strin vmrPath, tmpPath, builder.Configuration[PcsConfiguration.GitHubToken], - builder.Configuration[PcsConfiguration.AzDOToken]); + azureDevOpsToken: null); } public static void AddVmrInitialization(this WebApplicationBuilder builder, string vmrUri) diff --git a/src/ProductConstructionService/ProductConstructionService.Api/appsettings.Development.json b/src/ProductConstructionService/ProductConstructionService.Api/appsettings.Development.json index d0ba399962..6be4292a94 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/appsettings.Development.json +++ b/src/ProductConstructionService/ProductConstructionService.Api/appsettings.Development.json @@ -27,6 +27,7 @@ "UseAzCliAuthentication": true }, "AzureDevOps": { - "default": {} + "default": { + } } } diff --git a/src/ProductConstructionService/ProductConstructionService.Api/appsettings.Staging.json b/src/ProductConstructionService/ProductConstructionService.Api/appsettings.Staging.json index e13aeebd70..6fad5c9776 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/appsettings.Staging.json +++ b/src/ProductConstructionService/ProductConstructionService.Api/appsettings.Staging.json @@ -24,7 +24,7 @@ }, "AzureDevOps": { "default": { - "ManagedIdentityId": "system" + "ManagedIdentityId": "1d43ba8a-c2a6-4fad-b064-6d8c16fc0745" } } } diff --git a/src/ProductConstructionService/ProductConstructionService.Api/appsettings.json b/src/ProductConstructionService/ProductConstructionService.Api/appsettings.json index 22a1d9fb99..3dde9665ce 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/appsettings.json +++ b/src/ProductConstructionService/ProductConstructionService.Api/appsettings.json @@ -11,11 +11,5 @@ "QueuePollTimeout": "00:01:00", "MaxJobRetries": 3, "QueueMessageInvisibilityTime": "00:01:00" - }, - "GitHubAuthentication": { - "SaveTokens": true, - "CallbackPath": "/signin/github", - "UserAgentProduct": "PCS", - "ClaimsIssuer": "github" } -} \ No newline at end of file +} diff --git a/test/Maestro.Web.Tests/DependencyRegistrationTests.cs b/test/Maestro.Web.Tests/DependencyRegistrationTests.cs index a70a180041..2e6d7af32e 100644 --- a/test/Maestro.Web.Tests/DependencyRegistrationTests.cs +++ b/test/Maestro.Web.Tests/DependencyRegistrationTests.cs @@ -24,7 +24,12 @@ public void AreDependenciesRegistered() { Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", Environments.Development); - var config = new ConfigurationBuilder(); + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "EntraAuthentication:UserRole", "Maestro.User" } + }); + var collection = new ServiceCollection(); // The only scenario we are worried about is when running in the ServiceHost @@ -49,6 +54,13 @@ public void AreDependenciesRegistered() startup.ConfigureServices(s); }, out string message, - additionalScopedTypes: controllerTypes).Should().BeTrue(message); + additionalScopedTypes: controllerTypes, + additionalExemptTypes: new[] + { + "Microsoft.Identity.Web.Resource.MicrosoftIdentityIssuerValidatorFactory", + "Maestro.Authentication.BarTokenAuthenticationHandler" + }) + + .Should().BeTrue(message); } } diff --git a/test/Maestro.Web.Tests/LoggingConfigurationTests.cs b/test/Maestro.Web.Tests/LoggingConfigurationTests.cs index 2d2ddb2804..9f72d2cc0b 100644 --- a/test/Maestro.Web.Tests/LoggingConfigurationTests.cs +++ b/test/Maestro.Web.Tests/LoggingConfigurationTests.cs @@ -74,7 +74,12 @@ private static TestData Setup() var telemetry = new List(); channel.Setup(s => s.Send(Capture.In(telemetry))); - var config = new ConfigurationBuilder(); + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "EntraAuthentication:UserRole", "Maestro.User" } + }); + var collection = new ServiceCollection(); collection.AddSingleton(channel.Object); collection.AddSingleton(); diff --git a/test/Maestro.Web.Tests/Maestro.Web.Tests.csproj b/test/Maestro.Web.Tests/Maestro.Web.Tests.csproj index bc6cd0522f..eb0c7fc60e 100644 --- a/test/Maestro.Web.Tests/Maestro.Web.Tests.csproj +++ b/test/Maestro.Web.Tests/Maestro.Web.Tests.csproj @@ -1,8 +1,6 @@ - - diff --git a/test/Microsoft.DotNet.Darc.Tests/DependencyRegistrationTests.cs b/test/Microsoft.DotNet.Darc.Tests/DependencyRegistrationTests.cs new file mode 100644 index 0000000000..88c07ae333 --- /dev/null +++ b/test/Microsoft.DotNet.Darc.Tests/DependencyRegistrationTests.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.DotNet.Darc.Options; +using Microsoft.DotNet.Internal.DependencyInjection.Testing; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; + +namespace Microsoft.DotNet.Darc.Tests; + +[TestFixture] +public class DependencyRegistrationTests +{ + [Test] + [Ignore("Test skipped because it hangs in CI. https://github.com/dotnet/arcade-services/issues/3745")] + public void AreDependenciesRegistered() + { + DependencyInjectionValidation.IsDependencyResolutionCoherent(services => + { + // Tests instantiating the operations + IEnumerable optionTypes = Program.GetOptions().Concat(Program.GetVmrOptions()); + foreach (Type optionType in optionTypes) + { + // Register the option type + services.AddTransient(optionType); + + var operationOption = (CommandLineOptions) Activator.CreateInstance(optionType); + operationOption.RegisterServices(services); + var provider = services.BuildServiceProvider(); + + // Verify we can create the operation + var operation = operationOption.GetOperation(provider); + operation.Should().NotBeNull($"Operation of {optionType.Name} could not be created"); + services.AddTransient(operation.GetType()); + } + }, + out string message).Should().BeTrue(message); + } +} diff --git a/test/Microsoft.DotNet.Darc.Tests/Microsoft.DotNet.Darc.Tests.csproj b/test/Microsoft.DotNet.Darc.Tests/Microsoft.DotNet.Darc.Tests.csproj index 537db0e6f3..3fb2ca9a29 100644 --- a/test/Microsoft.DotNet.Darc.Tests/Microsoft.DotNet.Darc.Tests.csproj +++ b/test/Microsoft.DotNet.Darc.Tests/Microsoft.DotNet.Darc.Tests.csproj @@ -2,6 +2,7 @@ + diff --git a/test/Microsoft.DotNet.Darc.Tests/Operations/GetBuildOperationTests.cs b/test/Microsoft.DotNet.Darc.Tests/Operations/GetBuildOperationTests.cs index c83ebabdf4..2df98493c6 100644 --- a/test/Microsoft.DotNet.Darc.Tests/Operations/GetBuildOperationTests.cs +++ b/test/Microsoft.DotNet.Darc.Tests/Operations/GetBuildOperationTests.cs @@ -3,6 +3,7 @@ #nullable enable +using Castle.Core.Logging; using FluentAssertions; using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Options; @@ -10,6 +11,7 @@ using Microsoft.DotNet.DarcLib; using Microsoft.DotNet.Maestro.Client.Models; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using System; @@ -24,15 +26,15 @@ namespace Microsoft.DotNet.Darc.Tests.Operations; public class GetBuildOperationTests { private ConsoleOutputIntercepter _consoleOutput = null!; - private ServiceCollection _services = null!; private Mock _barMock = null!; + private Mock> _loggerMock = null!; [SetUp] public void Setup() { _consoleOutput = new(); _barMock = new Mock(); - _services = new ServiceCollection(); + _loggerMock = new(); } [TearDown] @@ -82,7 +84,6 @@ public async Task GetBuildOperationShouldHandleDuplicateBuilds() .ReturnsAsync(subscriptions.AsEnumerable()); _barMock.Setup(t => t.GetBuildsAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(builds.AsEnumerable()); - _services.AddSingleton(_barMock.Object); GetBuildCommandLineOptions options = new() { @@ -90,7 +91,7 @@ public async Task GetBuildOperationShouldHandleDuplicateBuilds() Commit = sha }; - GetBuildOperation getBuildOperation = new(options, _services); + GetBuildOperation getBuildOperation = new(options, _barMock.Object, _loggerMock.Object); int result = await getBuildOperation.ExecuteAsync(); @@ -129,14 +130,12 @@ public async Task GetBuildOperationShouldFetchById() _barMock.Setup(t => t.GetBuildAsync(It.IsAny())) .ReturnsAsync(build); - _services.AddSingleton(_barMock.Object); - GetBuildCommandLineOptions options = new() { Id = buildId }; - GetBuildOperation getBuildOperation = new(options, _services); + GetBuildOperation getBuildOperation = new(options, _barMock.Object, _loggerMock.Object); int result = await getBuildOperation.ExecuteAsync(); @@ -155,14 +154,12 @@ public async Task GetBuildOperationShouldWorkWhenDoseNotFindId() _barMock.Setup(t => t.GetBuildAsync(It.IsAny())) .Throws(new Exception()); - _services.AddSingleton(_barMock.Object); - GetBuildCommandLineOptions options = new() { Id = buildId }; - GetBuildOperation getBuildOperation = new(options, _services); + GetBuildOperation getBuildOperation = new(options, _barMock.Object, _loggerMock.Object); int result = await getBuildOperation.ExecuteAsync(); diff --git a/test/Microsoft.DotNet.Darc.Tests/Operations/GetSubscriptionsOperationTests.cs b/test/Microsoft.DotNet.Darc.Tests/Operations/GetSubscriptionsOperationTests.cs index c4aedb8de4..ce3301085c 100644 --- a/test/Microsoft.DotNet.Darc.Tests/Operations/GetSubscriptionsOperationTests.cs +++ b/test/Microsoft.DotNet.Darc.Tests/Operations/GetSubscriptionsOperationTests.cs @@ -16,6 +16,7 @@ using Microsoft.DotNet.Maestro.Client; using Microsoft.DotNet.Maestro.Client.Models; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using VerifyNUnit; @@ -26,17 +27,16 @@ namespace Microsoft.DotNet.Darc.Tests.Operations; public class GetSubscriptionsOperationTests { private ConsoleOutputIntercepter _consoleOutput = null!; - private ServiceCollection _services = null!; private Mock _barMock = null!; - + private Mock> _loggerMock = null!; [SetUp] public void Setup() { _consoleOutput = new(); - _barMock = new Mock(); - _services = new ServiceCollection(); + _barMock = new(); + _loggerMock = new(); } [TearDown] @@ -48,9 +48,7 @@ public void Teardown() [Test] public async Task GetSubscriptionsOperationTests_ExecuteAsync_returns_ErrorCode_for_empty_set() { - _services.AddSingleton(_barMock.Object); - - var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions(), _services); + var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions(), _barMock.Object, _loggerMock.Object); int result = await operation.ExecuteAsync(); @@ -65,9 +63,8 @@ public async Task GetSubscriptionsOperationTests_ExecuteAsync_returns_ErrorCode_ { _barMock.Setup(t => t.GetDefaultChannelsAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new AuthenticationException("boo.")); - _services.AddSingleton(_barMock.Object); - var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions(), _services); + var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions(), _barMock.Object, _loggerMock.Object); int result = await operation.ExecuteAsync(); @@ -82,9 +79,8 @@ public async Task GetSubscriptionsOperationTests_ExecuteAsync_returns_ErrorCode_ { _barMock.Setup(t => t.GetDefaultChannelsAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new Exception("foo.")); - _services.AddSingleton(_barMock.Object); - var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions(), _services); + var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions(), _barMock.Object, _loggerMock.Object); int result = await operation.ExecuteAsync(); @@ -115,9 +111,8 @@ public async Task GetSubscriptionsOperationTests_ExecuteAsync_returns_text() _barMock .Setup(t => t.GetSubscriptionsAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(subscriptions.AsEnumerable()); - _services.AddSingleton(_barMock.Object); - var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions(), _services); + var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions(), _barMock.Object, _loggerMock.Object); int result = await operation.ExecuteAsync(); @@ -146,9 +141,8 @@ public async Task GetSubscriptionsOperationTests_ExecuteAsync_returns_json() _barMock.Setup(t => t.GetSubscriptionsAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(subscriptions.AsEnumerable()); - _services.AddSingleton(_barMock.Object); - var operation = new GetSubscriptionsOperation(new() { OutputFormat = DarcOutputType.json }, _services); + var operation = new GetSubscriptionsOperation(new GetSubscriptionsCommandLineOptions { OutputFormat = DarcOutputType.json }, _barMock.Object, _loggerMock.Object); int result = await operation.ExecuteAsync(); @@ -187,14 +181,14 @@ public async Task GetSubscriptionsOperationTests_ExecuteAsync_returns_sorted_tex _barMock.Setup(t => t.GetSubscriptionsAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(subscriptions.AsEnumerable()); - _services.AddSingleton(_barMock.Object); var operation = new GetSubscriptionsOperation( new GetSubscriptionsCommandLineOptions() { OutputFormat = DarcOutputType.text }, - _services); + _barMock.Object, + _loggerMock.Object); int result = await operation.ExecuteAsync(); diff --git a/test/Microsoft.DotNet.Darc.Tests/Operations/OperationsTests.cs b/test/Microsoft.DotNet.Darc.Tests/Operations/OperationsTests.cs index 310e813e0c..9ffe92cc59 100644 --- a/test/Microsoft.DotNet.Darc.Tests/Operations/OperationsTests.cs +++ b/test/Microsoft.DotNet.Darc.Tests/Operations/OperationsTests.cs @@ -4,104 +4,55 @@ using FluentAssertions; using Microsoft.DotNet.Darc.Operations; using Microsoft.DotNet.Darc.Options; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using System; -using System.Threading.Tasks; namespace Microsoft.DotNet.Darc.Tests.Operations; [TestFixture] public class OperationTests { + public const DarcOutputType YmlDarcOutputType = (DarcOutputType)0xFF; [Test] public void OperationTests_IsOutputFormatSupported_default_should_not_throw() { - MockCommandLineOptions options = new(); + GetBuildCommandLineOptions options = new(); options.OutputFormat.Should().Be(DarcOutputType.text); - TextOutputOperation operation = new(options); - // If we got this far - all good - operation.Should().NotBeNull(); + options.Should().NotBeNull(); } [Test] public void OperationTests_IsOutputFormatSupported_should_throw_if_outputFormat_not_supported() { - MockCommandLineOptions options = new() - { - OutputFormat = DarcOutputType.json, - }; - ((Action)(() => _ = new TextOutputOperation(options))).Should() - .Throw() - .WithMessage("Output format type 'json' not yet supported for this operation.\r\nPlease raise a new issue in https://github.com/dotnet/arcade/issues/."); + ((Action)(() => _ = new FakeCommandLineOptions { OutputFormat = YmlDarcOutputType })).Should() + .Throw(); } [TestCase(DarcOutputType.text)] [TestCase(DarcOutputType.json)] - [TestCase(YmlJsonOutputOperation.YmlDarcOutputType)] public void OperationTests_IsOutputFormatSupported_should_not_throw_if_outputFormat_supported(DarcOutputType outputFormat) { - MockCommandLineOptions options = new() + FakeCommandLineOptions options = new() { OutputFormat = outputFormat, }; - YmlJsonOutputOperation operation = new(options); - // If we got this far - all good - operation.Should().NotBeNull(); - } - - private class TextOutputOperation : Operation - { - public TextOutputOperation(CommandLineOptions options) - : base(options) - { - } - - public override Task ExecuteAsync() => throw new NotImplementedException(); + options.Should().NotBeNull(); } - private class JsonOutputOperation : Operation + public class FakeCommandLineOptions : CommandLineOptions { - public JsonOutputOperation(CommandLineOptions options) - : base(options) - { - } - - public override Task ExecuteAsync() => throw new NotImplementedException(); - - protected override bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch + public override Operation GetOperation(ServiceProvider sp) => throw new NotImplementedException(); + public override bool IsOutputFormatSupported() + => OutputFormat switch { DarcOutputType.json => true, - _ => base.IsOutputFormatSupported(outputFormat), + _ => base.IsOutputFormatSupported(), }; } - - private class YmlJsonOutputOperation : JsonOutputOperation - { - public const DarcOutputType YmlDarcOutputType = (DarcOutputType)0xFF; - - public YmlJsonOutputOperation(CommandLineOptions options) - : base(options) - { - } - - public override Task ExecuteAsync() => throw new NotImplementedException(); - - protected override bool IsOutputFormatSupported(DarcOutputType outputFormat) - => outputFormat switch - { - YmlDarcOutputType => true, - _ => base.IsOutputFormatSupported(outputFormat), - }; - } - - private class MockCommandLineOptions : CommandLineOptions - { - public override Operation GetOperation() => throw new NotImplementedException(); - } } diff --git a/test/ProductConstructionService.Api.Tests/DependencyRegistrationTests.cs b/test/ProductConstructionService.Api.Tests/DependencyRegistrationTests.cs index 6371ae7b01..7e219c475d 100644 --- a/test/ProductConstructionService.Api.Tests/DependencyRegistrationTests.cs +++ b/test/ProductConstructionService.Api.Tests/DependencyRegistrationTests.cs @@ -57,6 +57,7 @@ public void AreDependenciesRegistered() "Microsoft.Extensions.ServiceDiscovery.Http.ServiceDiscoveryHttpMessageHandlerFactory", "Microsoft.Extensions.ServiceDiscovery.ServiceEndPointWatcherFactory", "Microsoft.Identity.Web.Resource.MicrosoftIdentityIssuerValidatorFactory", + "Maestro.Authentication.BarTokenAuthenticationHandler", ]).Should().BeTrue(message); } }