From 91980817e09e410910bff81249ef5cbb655272c9 Mon Sep 17 00:00:00 2001 From: Johannes Oenema Effectory Date: Wed, 5 Nov 2025 15:18:52 +0000 Subject: [PATCH] Merged PR 63702: Add Sonar Client to update permissions and tags in Sonar Projects to new team structure Add Sonar Client to update permissions and tags in Sonar Projects to new team structure Related work items: #125680 --- .aiignore | 38 ++ .gitignore | 334 ++++++++++++++++++ ConsoleApps/.gitignore | 334 ------------------ .../AzureRestApi/AzureRestApi.csproj | 18 +- .../AzureRestApi/Models/Api/ApiResponse.cs | 11 +- .../AzureRestApi/Models/Api/TagProperties.cs | 11 +- .../Models/Resources/Deployment.cs | 19 +- .../Models/Resources/DeploymentProperties.cs | 31 +- .../AzureRestApi/Models/Resources/Resource.cs | 43 ++- .../Models/Resources/ResourceGroup.cs | 21 +- .../Resources/ResourceGroupProperties.cs | 11 +- .../Models/Resources/Subscription.cs | 31 +- .../AzureRestApi/Models/Resources/TagTags.cs | 11 +- .../AzureRestApi/Models/Settings.cs | 13 +- .../AzureRestApi/AzureRestApi/Program.cs | 51 ++- .../Repositories/AccessTokenRepository.cs | 56 +-- .../Repositories/AzureBaseRepository.cs | 54 ++- .../Repositories/AzureDeploymentRepository.cs | 37 +- .../AzureResourceGroupRepository.cs | 24 +- .../Repositories/AzureResourceRepository.cs | 27 +- .../AzureSubscriptionRepository.cs | 43 +-- .../Repositories/AzureTagRepository.cs | 68 ++-- .../Services/CreatedOnDateService.cs | 96 +++-- .../Services/DeploymentTypeService.cs | 108 +++--- .../AzureRestApi/Services/OptionService.cs | 91 ++--- .../ConsoleApp1/ConsoleApp1.csproj | 8 - .../AzureRestApi/ConsoleApp1/Program.cs | 12 - .../SnykRestApi/Models/Parsed/AuditLog.cs | 27 +- .../Models/Raw/AuditLogResponse.cs | 37 +- .../SnykRestApi/Models/Raw/GroupResponse.cs | 19 +- .../Models/Raw/OrganizationListResponse.cs | 13 +- .../Models/Raw/OrganizationResponse.cs | 31 +- .../Models/Raw/ProjectListResponse.cs | 17 +- .../SnykRestApi/Models/Raw/ProjectResponse.cs | 17 +- .../SnykRestApi/Models/Raw/UserResponse.cs | 27 +- .../SnykRestApi/Models/Settings.cs | 19 +- .../SnykRestApi/SnykRestApi/Program.cs | 49 ++- .../Repositories/AccessTokenRepository.cs | 33 +- .../Repositories/AuditLogRepository.cs | 62 ++-- .../SnykRestApi/Repositories/CsvRepository.cs | 33 +- .../Repositories/OrganizationRepository.cs | 44 +-- .../Repositories/ProjectRepository.cs | 44 +-- .../Repositories/UserRepository.cs | 77 ++-- .../SnykRestApi/Services/AuditLogService.cs | 174 +++++---- .../SnykRestApi/Services/OptionService.cs | 78 ++-- .../SnykRestApi/SnykRestApi.csproj | 20 +- ConsoleApps/SonarClient/SonarClient.sln | 16 + .../Actions/UpdatePermissionsAndTags.cs | 93 +++++ .../SonarClient/Constants/SonarConstants.cs | 10 + .../SonarClient/SonarClient/Models/Link.cs | 8 + .../SonarClient/SonarClient/Models/Paging.cs | 9 + .../SonarClient/Models/PermissionTemplate.cs | 7 + .../Models/PermissionTemplateResponse.cs | 6 + .../SonarClient/SonarClient/Models/Project.cs | 16 + .../Models/ProjectsSearchResponse.cs | 7 + .../SonarClient/SonarClient/Program.cs | 59 ++++ .../Interfaces/IPermissionRepository.cs | 9 + .../Interfaces/IProjectRepository.cs | 8 + .../Interfaces/IProjectTagRepository.cs | 6 + .../Repositories/PermissionRepository.cs | 28 ++ .../Repositories/ProjectRepository.cs | 34 ++ .../Repositories/ProjectTagRepository.cs | 22 ++ .../SonarClient/SonarClient.csproj | 24 ++ .../SonarClient/SonarClient/appsettings.json | 7 + .../SonarClient/SonarClient/sonar-client.md | 44 +++ _azuredevops/cloud-engineering.yml | 47 +++ 66 files changed, 1586 insertions(+), 1296 deletions(-) create mode 100644 .aiignore delete mode 100644 ConsoleApps/.gitignore delete mode 100644 ConsoleApps/AzureRestApi/ConsoleApp1/ConsoleApp1.csproj delete mode 100644 ConsoleApps/AzureRestApi/ConsoleApp1/Program.cs create mode 100644 ConsoleApps/SonarClient/SonarClient.sln create mode 100644 ConsoleApps/SonarClient/SonarClient/Actions/UpdatePermissionsAndTags.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Constants/SonarConstants.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Models/Link.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Models/Paging.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Models/PermissionTemplate.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Models/PermissionTemplateResponse.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Models/Project.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Models/ProjectsSearchResponse.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Program.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IPermissionRepository.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IProjectRepository.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IProjectTagRepository.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Repositories/PermissionRepository.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Repositories/ProjectRepository.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/Repositories/ProjectTagRepository.cs create mode 100644 ConsoleApps/SonarClient/SonarClient/SonarClient.csproj create mode 100644 ConsoleApps/SonarClient/SonarClient/appsettings.json create mode 100644 ConsoleApps/SonarClient/SonarClient/sonar-client.md create mode 100644 _azuredevops/cloud-engineering.yml diff --git a/.aiignore b/.aiignore new file mode 100644 index 0000000..91b204d --- /dev/null +++ b/.aiignore @@ -0,0 +1,38 @@ +# Ignore environment and config files +environment.ts +environment.*.ts +environment.prod.ts +config.json +local.settings.json +local.settings.Designer.cs +settings.json +.env +auth-config.ts +assets/config/ +proxy.conf.json +package.json +angular.json +appsettings.json +appsettings.*.json +web.config +app.config +secrets.json +.env +launchSettings.json +Properties/PublishProfiles/*.pubxml +ServiceConfiguration.*.cscfg +App_Data/ +*.csproj +*.mdf +*.pfx +*.p12 +*.crt +*.key +.DS_Store +*.log +*.tmp +bin/ +obj/ +dist/ +build/ +out/ diff --git a/.gitignore b/.gitignore index 1f8ddbb..0f19d08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,337 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# Bicep output +Authorization.Deploy/authorization.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +#**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + Powershell/Tools/temp.ps1 Powershell/Tools/temp.json .vscode/settings.json diff --git a/ConsoleApps/.gitignore b/ConsoleApps/.gitignore deleted file mode 100644 index 1d24046..0000000 --- a/ConsoleApps/.gitignore +++ /dev/null @@ -1,334 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# Bicep output -Authorization.Deploy/authorization.json - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -#**/Properties/launchSettings.json - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj b/ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj index 63f6036..f735c1a 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj +++ b/ConsoleApps/AzureRestApi/AzureRestApi/AzureRestApi.csproj @@ -2,20 +2,20 @@ Exe - net6.0 + net8.0 enable enable - - - - - - - - + + + + + + + + diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Api/ApiResponse.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Api/ApiResponse.cs index 7424f55..efa59e9 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Api/ApiResponse.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Api/ApiResponse.cs @@ -1,7 +1,6 @@ -namespace AzureRestApi.Models.Api +namespace AzureRestApi.Models.Api; + +public class ApiResponse { - public class ApiResponse - { - public List? value { get; set; } - } -} + public List? value { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Api/TagProperties.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Api/TagProperties.cs index 1d7c37c..1771b59 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Api/TagProperties.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Api/TagProperties.cs @@ -1,9 +1,8 @@ using AzureRestApi.Models.Resources; -namespace AzureRestApi.Models.Api +namespace AzureRestApi.Models.Api; + +public class TagProperties { - public class TagProperties - { - public TagTags properties { get; set; } = new TagTags(); - } -} + public TagTags properties { get; set; } = new TagTags(); +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Deployment.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Deployment.cs index 8f985a8..4e1aaad 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Deployment.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Deployment.cs @@ -1,11 +1,10 @@ -namespace AzureRestApi.Models.Resources +namespace AzureRestApi.Models.Resources; + +public class Deployment { - public class Deployment - { - public string? id { get; set; } - public string? name { get; set; } - public string? type { get; set; } - public string? location { get; set; } - public DeploymentProperties? properties { get; set; } - } -} + public string? id { get; set; } + public string? name { get; set; } + public string? type { get; set; } + public string? location { get; set; } + public DeploymentProperties? properties { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/DeploymentProperties.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/DeploymentProperties.cs index b4c1267..1ca063a 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/DeploymentProperties.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/DeploymentProperties.cs @@ -1,17 +1,16 @@ -namespace AzureRestApi.Models.Resources +namespace AzureRestApi.Models.Resources; + +public class DeploymentProperties { - public class DeploymentProperties - { - public string? templateHash { get; set; } - public string? mode { get; set; } - //public string? parameters { get; set; } - public string? provisioningState { get; set; } - public DateTimeOffset? timestamp { get; set; } - public string? duration { get; set; } - public string? correlationId { get; set; } - // public string? providers { get; set; } - //public string? dependencies { get; set; } - //public string? outputs { get; set; } - public List? outputResources { get; set; } - } -} + public string? templateHash { get; set; } + public string? mode { get; set; } + //public string? parameters { get; set; } + public string? provisioningState { get; set; } + public DateTimeOffset? timestamp { get; set; } + public string? duration { get; set; } + public string? correlationId { get; set; } + // public string? providers { get; set; } + //public string? dependencies { get; set; } + //public string? outputs { get; set; } + public List? outputResources { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Resource.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Resource.cs index f1d0bd8..95d5291 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Resource.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Resource.cs @@ -1,28 +1,27 @@ -namespace AzureRestApi.Models.Resources -{ - public class Resource - { - public string? id { get; set; } - public string? name { get; set; } - public string? type { get; set; } - public string? location { get; set; } - public string? createdTime { get; set; } - public string? changedTime { get; set; } - public Dictionary tags { get; set; } = new Dictionary(); +namespace AzureRestApi.Models.Resources; - public string? CreatedOn +public class Resource +{ + public string? id { get; set; } + public string? name { get; set; } + public string? type { get; set; } + public string? location { get; set; } + public string? createdTime { get; set; } + public string? changedTime { get; set; } + public Dictionary tags { get; set; } = new Dictionary(); + + public string? CreatedOn + { + get { - get + if (string.IsNullOrWhiteSpace(createdTime)) { - if (string.IsNullOrWhiteSpace(createdTime)) - { - return changedTime; - } - else - { - return createdTime; - } + return changedTime; + } + else + { + return createdTime; } } } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/ResourceGroup.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/ResourceGroup.cs index a0f242e..abdddf9 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/ResourceGroup.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/ResourceGroup.cs @@ -1,12 +1,11 @@ -namespace AzureRestApi.Models.Resources +namespace AzureRestApi.Models.Resources; + +public class ResourceGroup { - public class ResourceGroup - { - public string? id { get; set; } - public string? name { get; set; } - public string? managedBy { get; set; } - public string? location { get; set; } - public Dictionary tags { get; set; } = new Dictionary(); - public DeploymentProperties? properties { get; set; } - } -} + public string? id { get; set; } + public string? name { get; set; } + public string? managedBy { get; set; } + public string? location { get; set; } + public Dictionary tags { get; set; } = new Dictionary(); + public DeploymentProperties? properties { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/ResourceGroupProperties.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/ResourceGroupProperties.cs index 4ebdd1a..a1b8eed 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/ResourceGroupProperties.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/ResourceGroupProperties.cs @@ -1,7 +1,6 @@ -namespace AzureRestApi.Models.Resources +namespace AzureRestApi.Models.Resources; + +public class ResourceGroupProperties { - public class ResourceGroupProperties - { - public string? provisioningState { get; set; } - } -} + public string? provisioningState { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Subscription.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Subscription.cs index 047f872..071880c 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Subscription.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/Subscription.cs @@ -1,20 +1,19 @@ -namespace AzureRestApi.Models.Resources -{ - public class Subscription - { - public string? id { get; set; } - public string? authorizationSource { get; set; } - public string? subscriptionId { get; set; } - public string? tenantId { get; set; } - public string? displayName { get; set; } - public string? state { get; set; } +namespace AzureRestApi.Models.Resources; - public bool Enabled +public class Subscription +{ + public string? id { get; set; } + public string? authorizationSource { get; set; } + public string? subscriptionId { get; set; } + public string? tenantId { get; set; } + public string? displayName { get; set; } + public string? state { get; set; } + + public bool Enabled + { + get { - get - { - return state == "Enabled"; - } + return state == "Enabled"; } } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/TagTags.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/TagTags.cs index dc4eb98..2b237a6 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/TagTags.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Resources/TagTags.cs @@ -1,7 +1,6 @@ -namespace AzureRestApi.Models.Resources +namespace AzureRestApi.Models.Resources; + +public class TagTags { - public class TagTags - { - public Dictionary tags { get; set; } = new Dictionary(); - } -} + public Dictionary tags { get; set; } = new Dictionary(); +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Settings.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Settings.cs index 27dda3d..13a8be2 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Models/Settings.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Models/Settings.cs @@ -1,8 +1,7 @@ -namespace AzureRestApi.Models +namespace AzureRestApi.Models; + +public class Settings { - public class Settings - { - public string KeyVaultName { get; set; } = string.Empty; - public string AzureTenantId { get; set; } = string.Empty; - } -} + public string KeyVaultName { get; set; } = string.Empty; + public string AzureTenantId { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Program.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Program.cs index e6f3bf1..dce62d7 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Program.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Program.cs @@ -5,37 +5,36 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace AzureRestApi +namespace AzureRestApi; + +class Program { - class Program - { - static Task Main(string[] args) => - CreateHostBuilder(args).Build().RunAsync(); + static Task Main(string[] args) => + CreateHostBuilder(args).Build().RunAsync(); - static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureServices((builder, services) => - { - IConfiguration config = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); + static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + { + IConfiguration config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .Build(); - Settings settings = config.GetRequiredSection("Settings").Get(); + Settings settings = config.GetRequiredSection("Settings").Get(); - services.AddSingleton(); - services.AddSingleton(settings); + services.AddSingleton(); + services.AddSingleton(settings); - services.AddHttpClient(); - services.AddHttpClient(); - services.AddHttpClient(); - services.AddHttpClient(); - services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddHostedService(); - } ); - } + services.AddHostedService(); + }); } \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AccessTokenRepository.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AccessTokenRepository.cs index 59b6d6c..bbd98d9 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AccessTokenRepository.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AccessTokenRepository.cs @@ -1,40 +1,40 @@ using Azure.Identity; using Azure.Security.KeyVault.Secrets; using AzureRestApi.Models; -using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.Identity.Client; -namespace AzureRestApi.Repositories +namespace AzureRestApi.Repositories; + +public class AccessTokenRepository(Settings settings) { - public class AccessTokenRepository + private string? _accessToken; + + public async Task GetAccessToken() { - private readonly Settings _settings; - private string? _accessToken; + if (!string.IsNullOrWhiteSpace(_accessToken)) return _accessToken; - public AccessTokenRepository(Settings settings) + var keyVaultUri = "https://" + settings.KeyVaultName + ".vault.azure.net"; + var credential = new DefaultAzureCredential(); + var client = new SecretClient(new Uri(keyVaultUri), credential); + var clientId = (await client.GetSecretAsync("ClientID")).Value.Value; + var clientSecret = (await client.GetSecretAsync("ClientSecret")).Value.Value; + + var app = ConfidentialClientApplicationBuilder + .Create(clientId) + .WithClientSecret(clientSecret) + .WithAuthority(new Uri($"https://login.microsoftonline.com/{settings.AzureTenantId}")) + .Build(); + + var scopes = new[] {"https://management.azure.com/.default"}; + + var result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); + if (result == null) { - _settings = settings; + throw new InvalidOperationException("Failed to obtain the Access token"); } - public async Task GetAccessToken() - { - if (!string.IsNullOrWhiteSpace(_accessToken)) return _accessToken; + _accessToken = result.AccessToken; - var keyvaultUri = "https://" + _settings.KeyVaultName + ".vault.azure.net"; - var credential = new DefaultAzureCredential(); - var client = new SecretClient(new Uri(keyvaultUri), credential); - var clientId = (await client.GetSecretAsync("ClientID")).Value.Value; - var clientSecret = (await client.GetSecretAsync("ClientSecret")).Value.Value; - - ClientCredential cc = new(clientId, clientSecret); - var context = new AuthenticationContext("https://login.microsoftonline.com/" + _settings.AzureTenantId); - var result = context.AcquireTokenAsync("https://management.azure.com/", cc); - if (result == null) - { - throw new InvalidOperationException("Failed to obtain the Access token"); - } - _accessToken = result.Result.AccessToken; - - return _accessToken; - } + return _accessToken; } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureBaseRepository.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureBaseRepository.cs index e1226bb..8012608 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureBaseRepository.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureBaseRepository.cs @@ -2,43 +2,33 @@ using System.Net.Http.Headers; using System.Text.Json; -namespace AzureRestApi.Repositories +namespace AzureRestApi.Repositories; + +public abstract class AzureBaseRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) { - public abstract class AzureBaseRepository + protected async Task> GetAllByUri(Uri uri) { - private readonly HttpClient _httpClient; - private readonly AccessTokenRepository _accessTokenRepository; + var accessToken = await accessTokenRepository.GetAccessToken(); - public AzureBaseRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) + HttpRequestMessage request = new(HttpMethod.Get, uri); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = await httpClient.SendAsync(request).ConfigureAwait(false); + + try { - _httpClient = httpClient; - _accessTokenRepository = accessTokenRepository; + response.EnsureSuccessStatusCode(); + + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonSerializer.Deserialize>(responseString)?.value; + return result ?? []; + } - - protected async Task> GetAllByUri(Uri uri) + catch { - var accessToken = await _accessTokenRepository.GetAccessToken(); - - HttpRequestMessage request = new(HttpMethod.Get, uri); - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var response = await _httpClient.SendAsync(request).ConfigureAwait(false); - - try - { - response.EnsureSuccessStatusCode(); - - var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var result = JsonSerializer.Deserialize>(responseString)?.value; - return result ?? new List(); - - } - catch - { - return new List(); - } + return []; } } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureDeploymentRepository.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureDeploymentRepository.cs index ed959af..05237a2 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureDeploymentRepository.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureDeploymentRepository.cs @@ -1,25 +1,20 @@ using AzureRestApi.Models.Resources; -namespace AzureRestApi.Repositories -{ - public class AzureDeploymentRepository : AzureBaseRepository - { - public AzureDeploymentRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository) - { - } +namespace AzureRestApi.Repositories; - public async Task> GetAllBySubscription(string subscriptionId) - { - // GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01 - var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01"); - return await GetAllByUri(uri); - } - public async Task> GetAllByResourceGroup(string subscriptionId, string resourceGroupName) - { - // GET https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01 - var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01"); - return await GetAllByUri(uri); - } - +public class AzureDeploymentRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) + : AzureBaseRepository(httpClient, accessTokenRepository) +{ + public async Task> GetAllBySubscription(string subscriptionId) + { + // GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01 + var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01"); + return await GetAllByUri(uri); } -} + public async Task> GetAllByResourceGroup(string subscriptionId, string resourceGroupName) + { + // GET https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01 + var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/Microsoft.Resources/deployments/?api-version=2021-04-01"); + return await GetAllByUri(uri); + } +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureResourceGroupRepository.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureResourceGroupRepository.cs index 48ebbec..5519bf9 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureResourceGroupRepository.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureResourceGroupRepository.cs @@ -1,18 +1,14 @@ using AzureRestApi.Models.Resources; -namespace AzureRestApi.Repositories -{ - public class AzureResourceGroupRepository : AzureBaseRepository - { - public AzureResourceGroupRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository) - { - } +namespace AzureRestApi.Repositories; - public async Task> GetAll(string subscriptionId) - { - // GET https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2021-04-01 - var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2021-04-01"); - return await GetAllByUri(uri); - } +public class AzureResourceGroupRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) + : AzureBaseRepository(httpClient, accessTokenRepository) +{ + public async Task> GetAll(string subscriptionId) + { + // GET https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2021-04-01 + var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2021-04-01"); + return await GetAllByUri(uri); } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureResourceRepository.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureResourceRepository.cs index dbd605d..d28f643 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureResourceRepository.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureResourceRepository.cs @@ -1,21 +1,14 @@ -using AzureRestApi.Models.Api; -using AzureRestApi.Models.Resources; -using System.Net.Http.Headers; -using System.Text.Json; +using AzureRestApi.Models.Resources; -namespace AzureRestApi.Repositories +namespace AzureRestApi.Repositories; + +public class AzureResourceRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) + : AzureBaseRepository(httpClient, accessTokenRepository) { - public class AzureResourceRepository : AzureBaseRepository + public async Task> GetAll(string subscriptionId) { - public AzureResourceRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) : base(httpClient, accessTokenRepository) - { - } - - public async Task> GetAll(string subscriptionId) - { - // GET https://management.azure.com/subscriptions/{subscriptionId}/resources?$filter={$filter}&$expand={$expand}&$top={$top}&api-version=2021-04-01 - var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resources?$expand=createdTime,changedTime&api-version=2021-04-01"); - return await GetAllByUri(uri); - } + // GET https://management.azure.com/subscriptions/{subscriptionId}/resources?$filter={$filter}&$expand={$expand}&$top={$top}&api-version=2021-04-01 + var uri = new Uri($"https://management.azure.com/subscriptions/{subscriptionId}/resources?$expand=createdTime,changedTime&api-version=2021-04-01"); + return await GetAllByUri(uri); } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureSubscriptionRepository.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureSubscriptionRepository.cs index 730ca5c..83b17f8 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureSubscriptionRepository.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureSubscriptionRepository.cs @@ -3,37 +3,26 @@ using AzureRestApi.Models.Resources; using System.Net.Http.Headers; using System.Text.Json; -namespace AzureRestApi.Repositories +namespace AzureRestApi.Repositories; + +public class AzureSubscriptionRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) { - public class AzureSubscriptionRepository + public async Task?> GetAll() { - private readonly HttpClient _httpClient; - private readonly AccessTokenRepository _accessTokenRepository; + // GET https://management.azure.com/subscriptions?api-version=2020-01-01 + var accessToken = await accessTokenRepository.GetAccessToken(); + var uri = new Uri("https://management.azure.com/subscriptions?api-version=2020-01-01"); - public AzureSubscriptionRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) - { - _httpClient = httpClient; - _accessTokenRepository = accessTokenRepository; - } + HttpRequestMessage request = new(HttpMethod.Get, uri); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - public async Task?> GetAll() - { - // GET https://management.azure.com/subscriptions?api-version=2020-01-01 + var response = await httpClient.SendAsync(request).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); - var accessToken = await _accessTokenRepository.GetAccessToken(); - var uri = new Uri("https://management.azure.com/subscriptions?api-version=2020-01-01"); + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - HttpRequestMessage request = new(HttpMethod.Get, uri); - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var response = await _httpClient.SendAsync(request).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - - var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - return JsonSerializer.Deserialize< ApiResponse>(responseString)?.value; - } + return JsonSerializer.Deserialize< ApiResponse>(responseString)?.value; } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureTagRepository.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureTagRepository.cs index e12eca4..e87c302 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureTagRepository.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Repositories/AzureTagRepository.cs @@ -4,50 +4,38 @@ using System.Net.Http.Headers; using System.Text; using System.Text.Json; -namespace AzureRestApi.Repositories +namespace AzureRestApi.Repositories; + +public class AzureTagRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository) { - public class AzureTagRepository + public async Task SetTag(string scope, string name, string value) { - private readonly HttpClient _httpClient; - private readonly AccessTokenRepository _accessTokenRepository; - private readonly ILogger _logger; + // PUT https://management.azure.com/{scope}/providers/Microsoft.Resources/tags/default?api-version=2021-04-01 + var result = true; + var accessToken = await accessTokenRepository.GetAccessToken(); + var uri = new Uri("https://management.azure.com/" + scope + "/providers/Microsoft.Resources/tags/default?api-version=2021-04-01"); - public AzureTagRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, ILogger logger) + var tagProperties = new TagProperties(); + tagProperties.properties.tags.Add(name, value); + var body = JsonSerializer.Serialize(tagProperties); + + HttpRequestMessage request = new(HttpMethod.Put, uri); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + + var response = await httpClient.SendAsync(request).ConfigureAwait(false); + try { - _httpClient = httpClient; - _accessTokenRepository = accessTokenRepository; - _logger = logger; + response.EnsureSuccessStatusCode(); + await response.Content.ReadAsStringAsync().ConfigureAwait(false); + } + catch + { + result = false; } - public async Task SetTag(string scope, string name, string value) - { - // PUT https://management.azure.com/{scope}/providers/Microsoft.Resources/tags/default?api-version=2021-04-01 - var result = true; - var accessToken = await _accessTokenRepository.GetAccessToken(); - var uri = new Uri("https://management.azure.com/" + scope + "/providers/Microsoft.Resources/tags/default?api-version=2021-04-01"); - - var tagProperties = new TagProperties(); - tagProperties.properties.tags.Add(name, value); - var body = JsonSerializer.Serialize(tagProperties); - - HttpRequestMessage request = new(HttpMethod.Put, uri); - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - request.Content = new StringContent(body, Encoding.UTF8, "application/json"); - - var response = await _httpClient.SendAsync(request).ConfigureAwait(false); - try - { - response.EnsureSuccessStatusCode(); - await response.Content.ReadAsStringAsync().ConfigureAwait(false); - } - catch - { - result = false; - } - - return result; - } + return result; } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Services/CreatedOnDateService.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Services/CreatedOnDateService.cs index 00e9c35..72f9b5d 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Services/CreatedOnDateService.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Services/CreatedOnDateService.cs @@ -1,65 +1,61 @@ using AzureRestApi.Repositories; using Spectre.Console; -namespace AzureRestApi.Services +namespace AzureRestApi.Services; + +public class CreatedOnDateService( + AzureSubscriptionRepository azureSubscriptionRepository, + AzureResourceRepository azureResourceRepository, + AzureTagRepository azureTagRepository) { - public class CreatedOnDateService + private const string TagName = "CreatedOnDate"; + + public async Task SetCreatedOnDateTags(bool skipDone = true) { - private const string TagName = "CreatedOnDate"; - private readonly AzureSubscriptionRepository _azureSubscriptionRepository; - private readonly AzureResourceRepository _azureResourceRepository; - private readonly AzureTagRepository _azureTagRepository; - - public CreatedOnDateService(AzureSubscriptionRepository azureSubscriptionRepository, AzureResourceRepository azureResourceRepository, AzureTagRepository azureTagRepository) + var rule = new Rule("[skyblue1]Setting CreatedOnDateTags[/]") { - _azureSubscriptionRepository = azureSubscriptionRepository; - _azureResourceRepository = azureResourceRepository; - _azureTagRepository = azureTagRepository; - } + Justification = Justify.Left, + Style = Style.Parse("skyblue1") + }; + AnsiConsole.Write(rule); - public async Task SetCreatedOnDateTags(bool skipDone = true) - { - var rule = new Rule("[skyblue1]Setting CreatedOnDateTags[/]"); - rule.Alignment = Justify.Left; - rule.Style = Style.Parse("skyblue1"); - AnsiConsole.Write(rule); + await AnsiConsole.Status() + .AutoRefresh(true) + .Spinner(Spinner.Known.Default) + .StartAsync("Retrieving subscriptions...", async ctx => + { + var subscriptions = await azureSubscriptionRepository.GetAll(); + if (subscriptions == null || subscriptions.Count == 0) throw new Exception("No subscriptions found"); + subscriptions = subscriptions.Where(s => s.Enabled && !string.IsNullOrWhiteSpace(s.subscriptionId)).ToList(); + if (subscriptions.Count == 0) throw new Exception("No subscriptions found"); - await AnsiConsole.Status() - .AutoRefresh(true) - .Spinner(Spinner.Known.Default) - .StartAsync("Retrieving subscriptions...", async ctx => + foreach (var subscription in subscriptions) { - var subscriptions = await _azureSubscriptionRepository.GetAll(); - if (subscriptions == null || !subscriptions.Any()) throw new Exception("No subscriptions found"); - subscriptions = subscriptions.Where(s => s.Enabled && !string.IsNullOrWhiteSpace(s.subscriptionId)).ToList(); - if (!subscriptions.Any()) throw new Exception("No subscriptions found"); - - foreach (var subscription in subscriptions) + rule = new Rule($"[skyblue1]{subscription.displayName}[/]") { - rule = new Rule($"[skyblue1]{subscription.displayName}[/]"); - rule.Alignment = Justify.Left; - rule.Style = Style.Parse("skyblue1 dim"); - AnsiConsole.Write(rule); + Justification = Justify.Left, + Style = Style.Parse("skyblue1 dim") + }; + AnsiConsole.Write(rule); - ctx.Status($"Getting the resources for subscription '{subscription.displayName}'"); + ctx.Status($"Getting the resources for subscription '{subscription.displayName}'"); - var resources = await _azureResourceRepository.GetAll(subscription.subscriptionId); - if (resources == null || !resources.Any()) continue; - resources = resources.Where(r => !string.IsNullOrWhiteSpace(r.CreatedOn) && !string.IsNullOrWhiteSpace(r.id)).ToList(); + var resources = await azureResourceRepository.GetAll(subscription.subscriptionId); + if (resources == null || resources.Count == 0) continue; + resources = resources.Where(r => !string.IsNullOrWhiteSpace(r.CreatedOn) && !string.IsNullOrWhiteSpace(r.id)).ToList(); - if (skipDone) - { - resources = resources.Where(r => !r.tags.Any(t => t.Key == TagName && !string.IsNullOrWhiteSpace(t.Value))).ToList(); - } - - ctx.Status($"Setting created date tag on resources in subscription '{subscription.displayName}'"); - foreach (var resource in resources) - { - var tagSuccess = await _azureTagRepository.SetTag(resource.id, TagName, resource.CreatedOn); - AnsiConsole.MarkupLine($"{resource.id} " + (tagSuccess ? "[green]V[/]" : "[red]X[/]")); - } + if (skipDone) + { + resources = resources.Where(r => !r.tags.Any(t => t.Key == TagName && !string.IsNullOrWhiteSpace(t.Value))).ToList(); } - }); - } + + ctx.Status($"Setting created date tag on resources in subscription '{subscription.displayName}'"); + foreach (var resource in resources) + { + var tagSuccess = await azureTagRepository.SetTag(resource.id, TagName, resource.CreatedOn); + AnsiConsole.MarkupLine($"{resource.id} " + (tagSuccess ? "[green]V[/]" : "[red]X[/]")); + } + } + }); } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Services/DeploymentTypeService.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Services/DeploymentTypeService.cs index 7687507..53918bf 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Services/DeploymentTypeService.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Services/DeploymentTypeService.cs @@ -1,73 +1,65 @@ using AzureRestApi.Repositories; using Spectre.Console; -namespace AzureRestApi.Services +namespace AzureRestApi.Services; + +public class DeploymentTypeService( + AzureSubscriptionRepository azureSubscriptionRepository, + AzureDeploymentRepository azureDeploymentRepository, + AzureResourceGroupRepository azureResourceGroupRepository) { - public class DeploymentTypeService + public async Task CheckDeploymentTypes() { - private readonly AzureSubscriptionRepository _azureSubscriptionRepository; - private readonly AzureDeploymentRepository _azureDeploymentRepository; - private readonly AzureResourceGroupRepository _azureResourceGroupRepository; - - public DeploymentTypeService(AzureSubscriptionRepository azureSubscriptionRepository, AzureDeploymentRepository azureDeploymentRepository, AzureResourceGroupRepository azureResourceGroupRepository) + var rule = new Rule("[skyblue1]Checking deployment types[/]") { - _azureSubscriptionRepository = azureSubscriptionRepository; - _azureDeploymentRepository = azureDeploymentRepository; - _azureResourceGroupRepository = azureResourceGroupRepository; - } + Justification = Justify.Left, + Style = Style.Parse("skyblue1") + }; + AnsiConsole.Write(rule); - public async Task CheckDeploymentTypes() - { - var rule = new Rule("[skyblue1]Checking deployment types[/]"); - rule.Alignment = Justify.Left; - rule.Style = Style.Parse("skyblue1"); - AnsiConsole.Write(rule); + await AnsiConsole.Status() + .AutoRefresh(true) + .Spinner(Spinner.Known.Default) + .StartAsync("Retrieving subscriptions...", async ctx => + { + // Retrieve subscriptions + var subscriptions = await azureSubscriptionRepository.GetAll(); + if (subscriptions == null || !subscriptions.Any()) throw new Exception("No subscriptions found"); + subscriptions = subscriptions.Where(s => s.Enabled && !string.IsNullOrWhiteSpace(s.subscriptionId)).ToList(); + if (subscriptions.Count == 0) throw new Exception("No subscriptions found"); - await AnsiConsole.Status() - .AutoRefresh(true) - .Spinner(Spinner.Known.Default) - .StartAsync("Retrieving subscriptions...", async ctx => + foreach (var subscription in subscriptions) { - // Retrieve subscriptions - var subscriptions = await _azureSubscriptionRepository.GetAll(); - if (subscriptions == null || !subscriptions.Any()) throw new Exception("No subscriptions found"); - subscriptions = subscriptions.Where(s => s.Enabled && !string.IsNullOrWhiteSpace(s.subscriptionId)).ToList(); - if (!subscriptions.Any()) throw new Exception("No subscriptions found"); - - foreach (var subscription in subscriptions) + rule = new Rule($"[skyblue1]{subscription.displayName}[/]") { - rule = new Rule($"[skyblue1]{subscription.displayName}[/]"); - rule.Alignment = Justify.Left; - rule.Style = Style.Parse("skyblue1 dim"); - AnsiConsole.Write(rule); - - ctx.Status($"Getting the deployments for subscription '{subscription.displayName}'"); - var deployments = await _azureDeploymentRepository.GetAllBySubscription(subscription.subscriptionId); - - if (deployments == null || !deployments.Any()) continue; - - foreach (var deployment in deployments) - { - AnsiConsole.WriteLine(Markup.Escape(deployment?.name)); - } - - ctx.Status($"Getting the resource groups for subscription '{subscription.displayName}'"); - var resourceGroups = await _azureResourceGroupRepository.GetAll(subscription.subscriptionId); - foreach (var resourceGroup in resourceGroups) - { - ctx.Status($"Getting the deployments for resourcegroup {resourceGroup.name} in subscription '{subscription.displayName}'"); - - var resourceGroupDeployments = await _azureDeploymentRepository.GetAllByResourceGroup(subscription.subscriptionId, resourceGroup.name); - foreach (var deployment in resourceGroupDeployments) - { - AnsiConsole.WriteLine(Markup.Escape($"{resourceGroup.name} - {deployment?.name}")); - } - } + Justification = Justify.Left, + Style = Style.Parse("skyblue1 dim") + }; + AnsiConsole.Write(rule); + ctx.Status($"Getting the deployments for subscription '{subscription.displayName}'"); + var deployments = await azureDeploymentRepository.GetAllBySubscription(subscription.subscriptionId); + if (deployments == null || deployments.Count == 0) continue; + foreach (var deployment in deployments) + { + AnsiConsole.WriteLine(Markup.Escape(deployment?.name)); } - }); - } + + ctx.Status($"Getting the resource groups for subscription '{subscription.displayName}'"); + var resourceGroups = await azureResourceGroupRepository.GetAll(subscription.subscriptionId); + foreach (var resourceGroup in resourceGroups) + { + ctx.Status($"Getting the deployments for resourcegroup {resourceGroup.name} in subscription '{subscription.displayName}'"); + + var resourceGroupDeployments = await azureDeploymentRepository.GetAllByResourceGroup(subscription.subscriptionId, resourceGroup.name); + foreach (var deployment in resourceGroupDeployments) + { + AnsiConsole.WriteLine(Markup.Escape($"{resourceGroup.name} - {deployment?.name}")); + } + } + } + }); } -} +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/AzureRestApi/Services/OptionService.cs b/ConsoleApps/AzureRestApi/AzureRestApi/Services/OptionService.cs index f39d931..ddd3224 100644 --- a/ConsoleApps/AzureRestApi/AzureRestApi/Services/OptionService.cs +++ b/ConsoleApps/AzureRestApi/AzureRestApi/Services/OptionService.cs @@ -1,64 +1,47 @@ using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Spectre.Console; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace AzureRestApi.Services +namespace AzureRestApi.Services; + +public class OptionService(CreatedOnDateService createdOnDateService, DeploymentTypeService deploymentTypeService) + : IHostedService { - public class OptionService : IHostedService + public async Task StartAsync(CancellationToken cancellationToken) { - private readonly ILogger _logger; - private readonly CreatedOnDateService _createdOnDateService; - private readonly DeploymentTypeService _deploymentTypeService; + var rule = new Rule("[yellow]Cloud Egineering Console App[/]"); + AnsiConsole.Write(rule); - public OptionService(ILogger logger, CreatedOnDateService createdOnDateService, DeploymentTypeService deploymentTypeService) + Console.WriteLine("-- This couldn't be done with Powershell, so here we are.... "); + Console.WriteLine(); + + var choices = new[] { - _logger = logger; - _createdOnDateService = createdOnDateService; - _deploymentTypeService = deploymentTypeService; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - var rule = new Rule("[yellow]Cloud Egineering Console App[/]"); - AnsiConsole.Write(rule); - - Console.WriteLine("-- This couldn't be done with Powershell, so here we are.... "); - Console.WriteLine(); - - var choices = new[] - { - "Update missing CreatedOnDate tags.", - "Update missing IaC Deployment type tags.", - "Exit" - }; - var result = AnsiConsole.Prompt(new SelectionPrompt() - .Title("Select what you want to do:") - .PageSize(10) - .MoreChoicesText("[grey](Move up and down to reveal more choices)[/]") - .AddChoices(choices)); - - if (result == choices[0]) - { - await _createdOnDateService.SetCreatedOnDateTags(true); - } - else if (result == choices[1]) - { - await _deploymentTypeService.CheckDeploymentTypes(); - } - - rule = new Rule("[yellow]Done. Bye.[/]"); - AnsiConsole.Write(rule); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); + "Update missing CreatedOnDate tags.", + "Update missing IaC Deployment type tags.", + "Exit" + }; + var result = AnsiConsole.Prompt(new SelectionPrompt() + .Title("Select what you want to do:") + .PageSize(10) + .MoreChoicesText("[grey](Move up and down to reveal more choices)[/]") + .AddChoices(choices)); + + if (result == choices[0]) + { + await createdOnDateService.SetCreatedOnDateTags(true); + } + else if (result == choices[1]) + { + await deploymentTypeService.CheckDeploymentTypes(); } + rule = new Rule("[yellow]Done. Bye.[/]"); + AnsiConsole.Write(rule); } -} + + public Task StopAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + +} \ No newline at end of file diff --git a/ConsoleApps/AzureRestApi/ConsoleApp1/ConsoleApp1.csproj b/ConsoleApps/AzureRestApi/ConsoleApp1/ConsoleApp1.csproj deleted file mode 100644 index c73e0d1..0000000 --- a/ConsoleApps/AzureRestApi/ConsoleApp1/ConsoleApp1.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - Exe - netcoreapp3.1 - - - diff --git a/ConsoleApps/AzureRestApi/ConsoleApp1/Program.cs b/ConsoleApps/AzureRestApi/ConsoleApp1/Program.cs deleted file mode 100644 index 75272b1..0000000 --- a/ConsoleApps/AzureRestApi/ConsoleApp1/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ConsoleApp1 -{ - internal class Program - { - static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } - } -} diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Parsed/AuditLog.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Parsed/AuditLog.cs index aeffca4..3037f2d 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Parsed/AuditLog.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Parsed/AuditLog.cs @@ -1,15 +1,14 @@ -namespace SnykRestApi.Models.Parsed +namespace SnykRestApi.Models.Parsed; + +public class AuditLog { - public class AuditLog - { - public string GroupId { get; set; } - public string OrganizationId { get; set; } - public string OrganizationName { get; set; } - public string UserId { get; set; } - public string UserName { get; set; } - public string ProjectId { get; set; } - public string ProjectName { get; set; } - public string Event { get; set; } - public DateTime Created { get; set; } - } -} + public string GroupId { get; set; } + public string OrganizationId { get; set; } + public string OrganizationName { get; set; } + public string UserId { get; set; } + public string UserName { get; set; } + public string ProjectId { get; set; } + public string ProjectName { get; set; } + public string Event { get; set; } + public DateTime Created { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/AuditLogResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/AuditLogResponse.cs index 812e0f4..b2accf5 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/AuditLogResponse.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/AuditLogResponse.cs @@ -1,24 +1,23 @@ using System.Text.Json.Serialization; -namespace SnykRestApi.Models.Raw +namespace SnykRestApi.Models.Raw; + +internal class AuditLogResponse { - internal class AuditLogResponse - { - [JsonPropertyName("groupId")] - public string GroupId { get; set; } - [JsonPropertyName("orgId")] - public string OrgId { get; set; } - [JsonPropertyName("userId")] - public string UserId { get; set; } - [JsonPropertyName("projectId")] - public string ProjectId { get; set; } - [JsonPropertyName("event")] - public string Event { get; set; } - //[JsonPropertyName("content")] - //public string Content { get; set; } - [JsonPropertyName("created")] - public DateTime Created { get; set; } + [JsonPropertyName("groupId")] + public string GroupId { get; set; } + [JsonPropertyName("orgId")] + public string OrgId { get; set; } + [JsonPropertyName("userId")] + public string UserId { get; set; } + [JsonPropertyName("projectId")] + public string ProjectId { get; set; } + [JsonPropertyName("event")] + public string Event { get; set; } + //[JsonPropertyName("content")] + //public string Content { get; set; } + [JsonPropertyName("created")] + public DateTime Created { get; set; } - } -} +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/GroupResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/GroupResponse.cs index 8e8a29f..42ace0f 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/GroupResponse.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/GroupResponse.cs @@ -1,13 +1,12 @@ using System.Text.Json.Serialization; -namespace SnykRestApi.Models.Raw -{ - internal class GroupResponse - { - [JsonPropertyName("name")] - public string Name { get; set; } - [JsonPropertyName("id")] - public string Id { get; set; } +namespace SnykRestApi.Models.Raw; - } -} +internal class GroupResponse +{ + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationListResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationListResponse.cs index 74eaf77..ad60b8f 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationListResponse.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationListResponse.cs @@ -1,10 +1,9 @@ using System.Text.Json.Serialization; -namespace SnykRestApi.Models.Raw +namespace SnykRestApi.Models.Raw; + +internal class OrganizationListResponse { - internal class OrganizationListResponse - { - [JsonPropertyName("orgs")] - public List Orgs { get; set; } - } -} + [JsonPropertyName("orgs")] + public List Orgs { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationResponse.cs index c868210..d89704a 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationResponse.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/OrganizationResponse.cs @@ -1,19 +1,18 @@ using System.Text.Json.Serialization; -namespace SnykRestApi.Models.Raw -{ - internal class OrganizationResponse - { - [JsonPropertyName("name")] - public string Name { get; set; } - [JsonPropertyName("id")] - public string Id { get; set; } - [JsonPropertyName("slug")] - public string Slug { get; set; } - [JsonPropertyName("url")] - public string Url { get; set; } - [JsonPropertyName("group")] - public GroupResponse Group { get; set; } +namespace SnykRestApi.Models.Raw; - } -} +internal class OrganizationResponse +{ + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("slug")] + public string Slug { get; set; } + [JsonPropertyName("url")] + public string Url { get; set; } + [JsonPropertyName("group")] + public GroupResponse Group { get; set; } + +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectListResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectListResponse.cs index 752795d..f36bbfe 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectListResponse.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectListResponse.cs @@ -1,12 +1,11 @@ using System.Text.Json.Serialization; -namespace SnykRestApi.Models.Raw +namespace SnykRestApi.Models.Raw; + +internal class ProjectListResponse { - internal class ProjectListResponse - { - [JsonPropertyName("org")] - public OrganizationResponse Org { get; set; } - [JsonPropertyName("projects")] - public List Projects { get; set; } - } -} + [JsonPropertyName("org")] + public OrganizationResponse Org { get; set; } + [JsonPropertyName("projects")] + public List Projects { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectResponse.cs index 0a200d0..8ad50ca 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectResponse.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/ProjectResponse.cs @@ -1,12 +1,11 @@ using System.Text.Json.Serialization; -namespace SnykRestApi.Models.Raw +namespace SnykRestApi.Models.Raw; + +internal class ProjectResponse { - internal class ProjectResponse - { - [JsonPropertyName("name")] - public string Name { get; set; } - [JsonPropertyName("id")] - public string Id { get; set; } - } -} + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/UserResponse.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/UserResponse.cs index 81b27c5..2351ca2 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/UserResponse.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Raw/UserResponse.cs @@ -1,17 +1,16 @@ using System.Text.Json.Serialization; -namespace SnykRestApi.Models.Raw -{ - internal class UserResponse - { - [JsonPropertyName("name")] - public string Name { get; set; } - [JsonPropertyName("id")] - public string Id { get; set; } - [JsonPropertyName("username")] - public string UserName { get; set; } - [JsonPropertyName("email")] - public string Email { get; set; } +namespace SnykRestApi.Models.Raw; - } -} +internal class UserResponse +{ + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("username")] + public string UserName { get; set; } + [JsonPropertyName("email")] + public string Email { get; set; } + +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Settings.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Settings.cs index 44ac9d7..280b9ea 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Models/Settings.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Models/Settings.cs @@ -1,11 +1,10 @@ -namespace SnykRestApi.Models -{ - public class Settings - { - public string KeyVaultName { get; set; } = string.Empty; - public string CsvFolder { get; set; } = string.Empty; - public string SnykBaseUrl { get; set; } = string.Empty; +namespace SnykRestApi.Models; - - } -} +public class Settings +{ + public string KeyVaultName { get; set; } = string.Empty; + public string CsvFolder { get; set; } = string.Empty; + public string SnykBaseUrl { get; set; } = string.Empty; + + +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Program.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Program.cs index 5181c43..872ecda 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Program.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Program.cs @@ -5,36 +5,35 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace SnykRestApi +namespace SnykRestApi; + +class Program { - class Program - { - static Task Main(string[] args) => - CreateHostBuilder(args).Build().RunAsync(); + static Task Main(string[] args) => + CreateHostBuilder(args).Build().RunAsync(); - static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureServices((builder, services) => - { - IConfiguration config = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); + static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices((builder, services) => + { + IConfiguration config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .Build(); - Settings settings = config.GetRequiredSection("Settings").Get(); + Settings settings = config.GetRequiredSection("Settings").Get(); - services.AddSingleton(); - services.AddSingleton(settings); + services.AddSingleton(); + services.AddSingleton(settings); - services.AddHttpClient(); - services.AddHttpClient(); - services.AddHttpClient(); - services.AddHttpClient(); - services.AddTransient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddTransient(); - services.AddScoped(); + services.AddScoped(); - services.AddHostedService(); - }); - } + services.AddHostedService(); + }); } \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AccessTokenRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AccessTokenRepository.cs index f9d3ba6..309cba4 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AccessTokenRepository.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AccessTokenRepository.cs @@ -2,28 +2,21 @@ using Azure.Security.KeyVault.Secrets; using SnykRestApi.Models; -namespace SnykRestApi.Repositories +namespace SnykRestApi.Repositories; + +public class AccessTokenRepository(Settings settings) { - public class AccessTokenRepository + private string? _authorizationToken = string.Empty; + + public async Task GetAuthorizationToken() { - private readonly Settings _settings; - private string? _authorizationToken = string.Empty; + if (!string.IsNullOrWhiteSpace(_authorizationToken)) return _authorizationToken; - public AccessTokenRepository(Settings settings) - { - _settings = settings; - } + var keyvaultUri = "https://" + settings.KeyVaultName + ".vault.azure.net"; + var credential = new DefaultAzureCredential(); + var client = new SecretClient(new Uri(keyvaultUri), credential); + _authorizationToken = (await client.GetSecretAsync("SnykKey")).Value.Value; - public async Task GetAuthorizationToken() - { - if (!string.IsNullOrWhiteSpace(_authorizationToken)) return _authorizationToken; - - var keyvaultUri = "https://" + _settings.KeyVaultName + ".vault.azure.net"; - var credential = new DefaultAzureCredential(); - var client = new SecretClient(new Uri(keyvaultUri), credential); - _authorizationToken = (await client.GetSecretAsync("SnykKey")).Value.Value; - - return _authorizationToken; - } + return _authorizationToken; } -} +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AuditLogRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AuditLogRepository.cs index ea64f56..37583bf 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AuditLogRepository.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/AuditLogRepository.cs @@ -3,47 +3,35 @@ using SnykRestApi.Models.Raw; using System.Net.Http.Headers; using System.Text.Json; -namespace SnykRestApi.Repositories +namespace SnykRestApi.Repositories; + +public class AuditLogRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) { - public class AuditLogRepository + internal async Task> GetByOrganizationId(string organizationId) { - private readonly HttpClient _httpClient; - private readonly AccessTokenRepository _accessTokenRepository; - private readonly Settings _settings; + var authorizationToken = await accessTokenRepository.GetAuthorizationToken(); + List result = []; + List responseItems; - public AuditLogRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) + var page = 0; + + do { - _httpClient = httpClient; - _accessTokenRepository = accessTokenRepository; - _settings = settings; + HttpRequestMessage request = new(HttpMethod.Post, $"{settings.SnykBaseUrl}org/{organizationId}/audit?from=2022-07-01&page={++page}"); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); + + var response = await httpClient.SendAsync(request).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + responseItems = JsonSerializer.Deserialize>(responseString) ?? new List(); + result.AddRange(responseItems); } + while (responseItems.Count == 100); - internal async Task> GetByOrganizationId(string origanizationId) - { - var authorizationToken = await _accessTokenRepository.GetAuthorizationToken(); - List result = new(); - List responseItems; - - int page = 0; - - do - { - HttpRequestMessage request = new(HttpMethod.Post, $"{_settings.SnykBaseUrl}org/{origanizationId}/audit?from=2022-07-01&page={++page}"); - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); - - var response = await _httpClient.SendAsync(request).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - responseItems = JsonSerializer.Deserialize>(responseString) ?? new List(); - result.AddRange(responseItems); - } - while (responseItems.Count == 100); - - return result; - } + return result; } -} +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/CsvRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/CsvRepository.cs index d988c77..d289c20 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/CsvRepository.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/CsvRepository.cs @@ -3,30 +3,17 @@ using SnykRestApi.Models; using SnykRestApi.Models.Parsed; using System.Globalization; -namespace SnykRestApi.Repositories +namespace SnykRestApi.Repositories; + +public class CsvRepository(Settings settings) { - public class CsvRepository + public async Task WriteAll (List log) { - private readonly Settings _settings; + var t = DateTime.Now; + var fileName = $"{settings.CsvFolder}SnykAuditLog_{t:yyyy}{t:MM}{t:dd}_{t:HH}{t:mm}{t:ss}_{t:FFF}.csv"; - public CsvRepository(Settings settings) - { - _settings = settings; - } - - public async Task WriteAll (List log) - { - var t = DateTime.Now; - var fileName = $"{_settings.CsvFolder}SnykAuditLog_{t:yyyy}{t:MM}{t:dd}_{t:HH}{t:mm}{t:ss}_{t:FFF}.csv"; - - using (var writer = new StreamWriter(fileName)) - using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) - { - await csv.WriteRecordsAsync(log); - } - - - - } + await using var writer = new StreamWriter(fileName); + await using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); + await csv.WriteRecordsAsync(log); } -} +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/OrganizationRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/OrganizationRepository.cs index e3dee25..431ed15 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/OrganizationRepository.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/OrganizationRepository.cs @@ -3,38 +3,26 @@ using SnykRestApi.Models.Raw; using System.Net.Http.Headers; using System.Text.Json; -namespace SnykRestApi.Repositories +namespace SnykRestApi.Repositories; + +public class OrganizationRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) { - public class OrganizationRepository + internal async Task> GetAll() { - private readonly HttpClient _httpClient; - private readonly AccessTokenRepository _accessTokenRepository; - private readonly Settings _settings; + var authorizationToken = await accessTokenRepository.GetAuthorizationToken(); - public OrganizationRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) - { - _httpClient = httpClient; - _accessTokenRepository = accessTokenRepository; - _settings = settings; - } + var uri = $"{settings.SnykBaseUrl}orgs"; + HttpRequestMessage request = new(HttpMethod.Get, uri); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); - internal async Task> GetAll() - { - var authorizationToken = await _accessTokenRepository.GetAuthorizationToken(); + var response = await httpClient.SendAsync(request).ConfigureAwait(false); - var uri = $"{_settings.SnykBaseUrl}orgs"; - HttpRequestMessage request = new(HttpMethod.Get, uri); - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); + response.EnsureSuccessStatusCode(); - var response = await _httpClient.SendAsync(request).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var result = JsonSerializer.Deserialize(responseString)?.Orgs; - return result ?? new List(); - } + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonSerializer.Deserialize(responseString)?.Orgs; + return result ?? []; } -} +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/ProjectRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/ProjectRepository.cs index 8c981f8..976db61 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/ProjectRepository.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/ProjectRepository.cs @@ -3,38 +3,26 @@ using SnykRestApi.Models.Raw; using System.Net.Http.Headers; using System.Text.Json; -namespace SnykRestApi.Repositories +namespace SnykRestApi.Repositories; + +public class ProjectRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) { - public class ProjectRepository + internal async Task> GetAll(string organizationId) { - private readonly HttpClient _httpClient; - private readonly AccessTokenRepository _accessTokenRepository; - private readonly Settings _settings; + var authorizationToken = await accessTokenRepository.GetAuthorizationToken(); - public ProjectRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) - { - _httpClient = httpClient; - _accessTokenRepository = accessTokenRepository; - _settings = settings; - } + var uri = $"{settings.SnykBaseUrl}org/{organizationId}/projects"; + HttpRequestMessage request = new(HttpMethod.Post, uri); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); - internal async Task> GetAll(string organizationId) - { - var authorizationToken = await _accessTokenRepository.GetAuthorizationToken(); + var response = await httpClient.SendAsync(request).ConfigureAwait(false); - var uri = $"{_settings.SnykBaseUrl}org/{organizationId}/projects"; - HttpRequestMessage request = new(HttpMethod.Post, uri); - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); + response.EnsureSuccessStatusCode(); - var response = await _httpClient.SendAsync(request).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var result = JsonSerializer.Deserialize(responseString)?.Projects; - return result ?? new List(); - } + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonSerializer.Deserialize(responseString)?.Projects; + return result ?? []; } -} +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/UserRepository.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/UserRepository.cs index 338dbc3..842c468 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/UserRepository.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Repositories/UserRepository.cs @@ -4,62 +4,49 @@ using Spectre.Console; using System.Net.Http.Headers; using System.Text.Json; -namespace SnykRestApi.Repositories +namespace SnykRestApi.Repositories; + +public class UserRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) { - public class UserRepository + internal async Task> GetAll(List ids) { - private readonly HttpClient _httpClient; - private readonly AccessTokenRepository _accessTokenRepository; - private readonly Settings _settings; + var authorizationToken = await accessTokenRepository.GetAuthorizationToken(); + List result = new(); - public UserRepository(HttpClient httpClient, AccessTokenRepository accessTokenRepository, Settings settings) + foreach (var id in ids) { - _httpClient = httpClient; - _accessTokenRepository = accessTokenRepository; - _settings = settings; - } + HttpRequestMessage request = new(HttpMethod.Get, $"{settings.SnykBaseUrl}user/{id}"); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); - internal async Task> GetAll(List ids) - { - var authorizationToken = await _accessTokenRepository.GetAuthorizationToken(); - List result = new(); - UserResponse? responseItem; + var response = await httpClient.SendAsync(request).ConfigureAwait(false); - foreach (var id in ids) + try { - HttpRequestMessage request = new(HttpMethod.Get, $"{_settings.SnykBaseUrl}user/{id}"); - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("token", authorizationToken); + response.EnsureSuccessStatusCode(); - var response = await _httpClient.SendAsync(request).ConfigureAwait(false); + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var responseItem = JsonSerializer.Deserialize(responseString); - try + if (responseItem != null) { - response.EnsureSuccessStatusCode(); - - var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - responseItem = JsonSerializer.Deserialize(responseString); - - if (responseItem != null) - { - result.Add(responseItem); - } - } - catch - { - AnsiConsole.MarkupLine($"[red bold]Could not find user with id '{id}'[/]"); - result.Add(new() - { - Id = id, - Name = "{ Unknown user }", - UserName = "{ Unknown user }", - Email = "{ Unknown user }" - }); + result.Add(responseItem); } } - - return result; + catch + { + AnsiConsole.MarkupLine($"[red bold]Could not find user with id '{id}'[/]"); + result.Add(new() + { + Id = id, + Name = "{ Unknown user }", + UserName = "{ Unknown user }", + Email = "{ Unknown user }" + }); + } } + + return result; } -} +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Services/AuditLogService.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Services/AuditLogService.cs index d6de2f5..b2c3932 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Services/AuditLogService.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Services/AuditLogService.cs @@ -2,108 +2,96 @@ using SnykRestApi.Models.Raw; using SnykRestApi.Repositories; using Spectre.Console; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace SnykRestApi.Services +namespace SnykRestApi.Services; + +public class AuditLogService( + OrganizationRepository organizationRepository, + AuditLogRepository auditLogRepository, + ProjectRepository projectRepository, + UserRepository userRepository, + CsvRepository csvRepository) { - public class AuditLogService + public async Task CreateAuditLog () { - private readonly OrganizationRepository _organizationRepository; - private readonly AuditLogRepository _auditLogRepository; - private readonly ProjectRepository _projectRepository; - private readonly UserRepository _userRepository; - private readonly CsvRepository _csvRepository; + var rule = new Rule("[skyblue1]Creating Snyk Audit Log CSV[/]"); + rule.Justification = Justify.Left; + rule.Style = Style.Parse("skyblue1"); + AnsiConsole.Write(rule); - public AuditLogService(OrganizationRepository organizationRepository, AuditLogRepository auditLogRepostitory, ProjectRepository projectRepository, UserRepository userRepository, CsvRepository csvRepository) - { - _organizationRepository = organizationRepository; - _auditLogRepository = auditLogRepostitory; - _projectRepository = projectRepository; - _userRepository = userRepository; - _csvRepository = csvRepository; - } + await AnsiConsole.Status() + .AutoRefresh(true) + .Spinner(Spinner.Known.Default) + .StartAsync("Retrieving organizations...", async ctx => + { + var organizations = await organizationRepository.GetAll(); + if (organizations == null || !organizations.Any()) throw new Exception("No organizations found"); - public async Task CreateAuditLog () - { - var rule = new Rule("[skyblue1]Creating Snyk Audit Log CSV[/]"); - rule.Alignment = Justify.Left; - rule.Style = Style.Parse("skyblue1"); - AnsiConsole.Write(rule); + var log = new List(); + var projects = new List(); - await AnsiConsole.Status() - .AutoRefresh(true) - .Spinner(Spinner.Known.Default) - .StartAsync("Retrieving organizations...", async ctx => + foreach (var organization in organizations) { - var organizations = await _organizationRepository.GetAll(); - if (organizations == null || !organizations.Any()) throw new Exception("No organizations found"); + rule = new Rule($"[skyblue1]{organization.Name}[/]"); + rule.Justification = Justify.Left; + rule.Style = Style.Parse("skyblue1 dim"); + AnsiConsole.Write(rule); - var log = new List(); - var projects = new List(); + ctx.Status($"Getting the projects for organization '{organization.Name}'"); + var orgProjects = await projectRepository.GetAll(organization.Id); + projects.AddRange(orgProjects); + AnsiConsole.WriteLine($"Got {orgProjects.Count} projects."); - foreach (var organization in organizations) + ctx.Status($"Getting the audit log for organization '{organization.Name}'"); + var orgLogs = await auditLogRepository.GetByOrganizationId(organization.Id); + log.AddRange(orgLogs); + AnsiConsole.WriteLine($"Got {orgLogs.Count} log records."); + } + + rule = new Rule("[skyblue1]Retrieving users[/]") + { + Justification = Justify.Left, + Style = Style.Parse("skyblue1 dim") + }; + AnsiConsole.Write(rule); + + ctx.Status($"Getting users"); + var userIds = log.Select(l => l.UserId).Distinct().ToList(); + + var users = await userRepository.GetAll(userIds); + AnsiConsole.WriteLine($"Got {users.Count} users of {userIds.Count} user ids."); + + rule = new Rule("[skyblue1]Creating CSV[/]") + { + Justification = Justify.Left, + Style = Style.Parse("skyblue1 dim") + }; + AnsiConsole.Write(rule); + + ctx.Status($"Combining all information"); + var result = (from l in log + join o in organizations on l.OrgId equals o.Id into gjO + from subO in gjO.DefaultIfEmpty() + join u in users on l.UserId equals u.Id into gjU + from subU in gjU.DefaultIfEmpty() + join p in projects on l.ProjectId equals p.Id into gjP + from subP in gjP.DefaultIfEmpty() + select new AuditLog { - rule = new Rule($"[skyblue1]{organization.Name}[/]"); - rule.Alignment = Justify.Left; - rule.Style = Style.Parse("skyblue1 dim"); - AnsiConsole.Write(rule); + GroupId = l.GroupId, + OrganizationId = l.OrgId, + OrganizationName = subO?.Name, + ProjectId = l.ProjectId, + ProjectName = subP?.Name, + UserId = l.UserId, + UserName = subU?.Name, + Event = l.Event, + Created = l.Created + }).ToList(); + AnsiConsole.WriteLine($"Prepared {result.Count} lines to export of {log.Count} audit log records."); - ctx.Status($"Getting the projects for organization '{organization.Name}'"); - var orgProjects = await _projectRepository.GetAll(organization.Id); - projects.AddRange(orgProjects); - AnsiConsole.WriteLine($"Got {orgProjects.Count} projects."); - - ctx.Status($"Getting the audit log for organization '{organization.Name}'"); - var orgLogs = await _auditLogRepository.GetByOrganizationId(organization.Id); - log.AddRange(orgLogs); - AnsiConsole.WriteLine($"Got {orgLogs.Count} log records."); - } - - rule = new Rule($"[skyblue1]Retrieving users[/]"); - rule.Alignment = Justify.Left; - rule.Style = Style.Parse("skyblue1 dim"); - AnsiConsole.Write(rule); - - ctx.Status($"Getting users"); - var userIds = log.Select(l => l.UserId).Distinct().ToList(); - - var users = await _userRepository.GetAll(userIds); - AnsiConsole.WriteLine($"Got {users.Count} users of {userIds.Count} user ids."); - - rule = new Rule($"[skyblue1]Creating CSV[/]"); - rule.Alignment = Justify.Left; - rule.Style = Style.Parse("skyblue1 dim"); - AnsiConsole.Write(rule); - - ctx.Status($"Combining all information"); - var result = (from l in log - join o in organizations on l.OrgId equals o.Id into gjO - from subO in gjO.DefaultIfEmpty() - join u in users on l.UserId equals u.Id into gjU - from subU in gjU.DefaultIfEmpty() - join p in projects on l.ProjectId equals p.Id into gjP - from subP in gjP.DefaultIfEmpty() - select new AuditLog() - { - GroupId = l.GroupId, - OrganizationId = l.OrgId, - OrganizationName = subO?.Name, - ProjectId = l.ProjectId, - ProjectName = subP?.Name, - UserId = l.UserId, - UserName = subU?.Name, - Event = l.Event, - Created = l.Created - }).ToList(); - AnsiConsole.WriteLine($"Prepared {result.Count} lines to export of {log.Count} audit log records."); - - ctx.Status($"Writing CSV"); - await _csvRepository.WriteAll(result); - }); - } + ctx.Status("Writing CSV"); + await csvRepository.WriteAll(result); + }); } -} +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/Services/OptionService.cs b/ConsoleApps/SnykRestApi/SnykRestApi/Services/OptionService.cs index cee5150..ecc11e4 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/Services/OptionService.cs +++ b/ConsoleApps/SnykRestApi/SnykRestApi/Services/OptionService.cs @@ -2,55 +2,47 @@ using Microsoft.Extensions.Logging; using Spectre.Console; -namespace SnykRestApi.Services +namespace SnykRestApi.Services; + +public class OptionService(AuditLogService auditLogService) + : IHostedService { - public class OptionService : IHostedService + + public async Task StartAsync(CancellationToken cancellationToken) { - private readonly ILogger _logger; - private readonly AuditLogService _auditLogService; + var rule = new Rule("[yellow]Cloud Egineering Console App[/]"); + AnsiConsole.Write(rule); - public OptionService(ILogger logger, AuditLogService auditLogService) + + Console.WriteLine("-- This couldn't be done in the Snyk UI, so here we are.... "); + Console.WriteLine(); + + var choices = new[] { - _logger = logger; - _auditLogService = auditLogService; - } + "Create Audit log CSV.", + "Exit" + }; + var result = AnsiConsole.Prompt(new SelectionPrompt() + .Title("Select what you want to do:") + .PageSize(10) + .MoreChoicesText("[grey](Move up and down to reveal more choices)[/]") + .AddChoices(choices)); - public async Task StartAsync(CancellationToken cancellationToken) + if (result == choices[0]) { - var rule = new Rule("[yellow]Cloud Egineering Console App[/]"); - AnsiConsole.Write(rule); - - - Console.WriteLine("-- This couldn't be done in the Snyk UI, so here we are.... "); - Console.WriteLine(); - - var choices = new[] - { - "Create Audit log CSV.", - "Exit" - }; - var result = AnsiConsole.Prompt(new SelectionPrompt() - .Title("Select what you want to do:") - .PageSize(10) - .MoreChoicesText("[grey](Move up and down to reveal more choices)[/]") - .AddChoices(choices)); - - if (result == choices[0]) - { - await _auditLogService.CreateAuditLog(); - } - //else if (result == choices[1]) - //{ - // // Do something - //} - - rule = new Rule("[yellow]Done. Bye.[/]"); - AnsiConsole.Write(rule); + await auditLogService.CreateAuditLog(); } + //else if (result == choices[1]) + //{ + // // Do something + //} - public Task StopAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + rule = new Rule("[yellow]Done. Bye.[/]"); + AnsiConsole.Write(rule); } -} + + public Task StopAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/ConsoleApps/SnykRestApi/SnykRestApi/SnykRestApi.csproj b/ConsoleApps/SnykRestApi/SnykRestApi/SnykRestApi.csproj index 4917a9d..4c0fe7d 100644 --- a/ConsoleApps/SnykRestApi/SnykRestApi/SnykRestApi.csproj +++ b/ConsoleApps/SnykRestApi/SnykRestApi/SnykRestApi.csproj @@ -2,21 +2,21 @@ Exe - net6.0 + net8.0 enable enable - - - - - - - - - + + + + + + + + + diff --git a/ConsoleApps/SonarClient/SonarClient.sln b/ConsoleApps/SonarClient/SonarClient.sln new file mode 100644 index 0000000..308f857 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarClient", "SonarClient\SonarClient.csproj", "{1DF21902-A886-4C02-AB59-B19CB291498D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1DF21902-A886-4C02-AB59-B19CB291498D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DF21902-A886-4C02-AB59-B19CB291498D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DF21902-A886-4C02-AB59-B19CB291498D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DF21902-A886-4C02-AB59-B19CB291498D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/ConsoleApps/SonarClient/SonarClient/Actions/UpdatePermissionsAndTags.cs b/ConsoleApps/SonarClient/SonarClient/Actions/UpdatePermissionsAndTags.cs new file mode 100644 index 0000000..e686dc0 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Actions/UpdatePermissionsAndTags.cs @@ -0,0 +1,93 @@ +using SonarClient.Repositories.Interfaces; +using Spectre.Console; + +namespace SonarClient.Actions; + +public class UpdatePermissionsAndTags( + IProjectRepository projectRepository, + IPermissionRepository permissionRepository, + IProjectTagRepository projectTagRepository) +{ + private Dictionary PermissionTemplateMapping { get; set; } = new(); + + public async Task Execute(string organizationId, string organizationKey) + { + var projects = await projectRepository.GetProjects(organizationId); + var permissionTemplates = await permissionRepository.GetPermissionTemplates(organizationKey); + PermissionTemplateMapping = permissionTemplates.ToDictionary(x => x.Name, x => x.Id); + + var projectTeams = new List(); + + foreach (var project in projects) + { + if (project.Tags.Length == 0) + { + AnsiConsole.MarkupLine($"[red]Project {project.Name} has zero tags[/]"); + continue; + } + + var oldTeam = project.Tags[0]; + + var newTeam = GetNewTeam(oldTeam); + + if (newTeam == null) + { + continue; + } + + projectTeams.Add(new ProjectTeam(project.Key, oldTeam, newTeam)); + } + + foreach (var teamProjects in projectTeams.GroupBy(p => p.OldTeam).ToDictionary(g => g.Key, g => g.ToList())) + { + AnsiConsole.MarkupLine($"Do you want to update permissions and tags for team: [yellow]{teamProjects.Key}[/] to [yellow]{GetNewTeam(teamProjects.Key)}[/]? It will be updated for the following projects:"); + foreach (var project in teamProjects.Value) + { + AnsiConsole.MarkupLine($"[green]{Markup.Escape($"- {project.ProjectKey}")}[/]"); + } + + if (!await AnsiConsole.ConfirmAsync("Confirm")) + { + continue; + } + + foreach (var project in teamProjects.Value) + { + await permissionRepository.ApplyTemplateToProject(project.ProjectKey, GetPermissionTemplateName(project.NewTeam)); + await projectTagRepository.SetTags(project.ProjectKey, [project.NewTeam]); + AnsiConsole.MarkupLine($"[green]Updated permissions and tags for project: [yellow]{project.ProjectKey}[/][/]"); + } + AnsiConsole.Clear(); + } + } + + private static string GetNewTeam(string oldTeam) + { + return oldTeam switch + { + "lime" => "platform", + "yellow" => "reporting", + "orange" => "reporting", + "red" => "surveying", + "pink" => "surveying", + "gray" => "surveying", + "blue" => "data-and-ai", + _ => null + }; + } + + private string GetPermissionTemplateName(string newTeam) + { + return newTeam switch + { + "platform" => PermissionTemplateMapping["Team Platform template"], + "reporting" => PermissionTemplateMapping["Team Reporting template"], + "surveying" => PermissionTemplateMapping["Team Surveying template"], + "data-and-ai" => PermissionTemplateMapping["Team Data and AI template"], + "managers" => PermissionTemplateMapping["Team Managers template"], + _ => throw new IndexOutOfRangeException($"Unknown team: {newTeam}") + }; + } + + private record ProjectTeam(string ProjectKey, string OldTeam, string NewTeam); +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Constants/SonarConstants.cs b/ConsoleApps/SonarClient/SonarClient/Constants/SonarConstants.cs new file mode 100644 index 0000000..e19876a --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Constants/SonarConstants.cs @@ -0,0 +1,10 @@ +namespace SonarClient.Constants; + +public static class SonarConstants +{ + public const string SonarApiV1ClientName = "SonarApiV1Client"; + public const string SonarApiV1BaseAddress = "https://sonarcloud.io/"; + + public const string SonarApiV2ClientName = "SonarApiV2Client"; + public const string SonarApiV2BaseAddress = "https://api.sonarcloud.io/"; +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Models/Link.cs b/ConsoleApps/SonarClient/SonarClient/Models/Link.cs new file mode 100644 index 0000000..76bffab --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Models/Link.cs @@ -0,0 +1,8 @@ +namespace SonarClient.Models; + +public class Link +{ + public string Name { get; set; } + public string Url { get; set; } + public string Type { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Models/Paging.cs b/ConsoleApps/SonarClient/SonarClient/Models/Paging.cs new file mode 100644 index 0000000..bb8080d --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Models/Paging.cs @@ -0,0 +1,9 @@ +namespace SonarClient.Models; + +public class Paging +{ + public int PageIndex { get; set; } + public int PageSize { get; set; } + public int Total { get; set; } +} + diff --git a/ConsoleApps/SonarClient/SonarClient/Models/PermissionTemplate.cs b/ConsoleApps/SonarClient/SonarClient/Models/PermissionTemplate.cs new file mode 100644 index 0000000..a8df073 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Models/PermissionTemplate.cs @@ -0,0 +1,7 @@ +namespace SonarClient.Models; + +public class PermissionTemplate +{ + public string Id { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Models/PermissionTemplateResponse.cs b/ConsoleApps/SonarClient/SonarClient/Models/PermissionTemplateResponse.cs new file mode 100644 index 0000000..f896dce --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Models/PermissionTemplateResponse.cs @@ -0,0 +1,6 @@ +namespace SonarClient.Models; + +public class PermissionTemplateResponse +{ + public List PermissionTemplates { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Models/Project.cs b/ConsoleApps/SonarClient/SonarClient/Models/Project.cs new file mode 100644 index 0000000..c474a43 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Models/Project.cs @@ -0,0 +1,16 @@ +namespace SonarClient.Models; + +public class Project +{ + public string Id { get; set; } + public string LegacyId { get; set; } + public string Key { get; set; } + public string Name { get; set; } + public string Visibility { get; set; } + public string OrganizationId { get; set; } + public string Description { get; set; } + public string[] Tags { get; set; } + public Link[] Links { get; set; } + public string CreatedAt { get; set; } + public string UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Models/ProjectsSearchResponse.cs b/ConsoleApps/SonarClient/SonarClient/Models/ProjectsSearchResponse.cs new file mode 100644 index 0000000..64fee06 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Models/ProjectsSearchResponse.cs @@ -0,0 +1,7 @@ +namespace SonarClient.Models; + +public class ProjectsSearchResponse +{ + public Paging Page { get; set; } + public List Projects { get; set; } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Program.cs b/ConsoleApps/SonarClient/SonarClient/Program.cs new file mode 100644 index 0000000..4166344 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Program.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using SonarClient.Actions; +using SonarClient.Constants; +using SonarClient.Repositories; +using SonarClient.Repositories.Interfaces; +using Spectre.Console; +using System.Net.Http.Headers; +using System.Text; + +var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddUserSecrets() + .AddEnvironmentVariables(); +var configurationRoot = builder.Build(); + +var sonarConfiguration = configurationRoot.GetSection("sonar"); + +var token = sonarConfiguration["api_key"]; +var organizationId = sonarConfiguration["organization_id"]; +var organizationKey = sonarConfiguration["organization_key"]; + +var services = new ServiceCollection(); + +services.AddLogging(options => +{ + options.SetMinimumLevel(LogLevel.Warning); + options.AddConsole(); +}); + +services.AddHttpClient(SonarConstants.SonarApiV1ClientName, c => +{ + c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{token}:"))); + c.BaseAddress = new Uri(SonarConstants.SonarApiV1BaseAddress); +}); +services.AddHttpClient(SonarConstants.SonarApiV2ClientName, c => +{ + c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + c.BaseAddress = new Uri(SonarConstants.SonarApiV2BaseAddress); +}); + +// Repositories +services.AddTransient(); +services.AddTransient(); +services.AddTransient(); + +// Actions +services.AddTransient(); + +await using var serviceProvider = services.BuildServiceProvider(); + +var updatePermissionsAndTags = serviceProvider.GetRequiredService(); + +AnsiConsole.Confirm("Are you sure you want to update permissions?", false); +await updatePermissionsAndTags.Execute(organizationId, organizationKey); + +Console.WriteLine("Press any key to exit..."); +Console.ReadKey(); \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IPermissionRepository.cs b/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IPermissionRepository.cs new file mode 100644 index 0000000..901a2e8 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IPermissionRepository.cs @@ -0,0 +1,9 @@ +using SonarClient.Models; + +namespace SonarClient.Repositories.Interfaces; + +public interface IPermissionRepository +{ + Task> GetPermissionTemplates(string organizationKey); + Task ApplyTemplateToProject(string projectKey, string templateId); +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IProjectRepository.cs b/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IProjectRepository.cs new file mode 100644 index 0000000..c3afb8c --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IProjectRepository.cs @@ -0,0 +1,8 @@ +using SonarClient.Models; + +namespace SonarClient.Repositories.Interfaces; + +public interface IProjectRepository +{ + Task> GetProjects(string organizationId); +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IProjectTagRepository.cs b/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IProjectTagRepository.cs new file mode 100644 index 0000000..e5503ce --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Repositories/Interfaces/IProjectTagRepository.cs @@ -0,0 +1,6 @@ +namespace SonarClient.Repositories.Interfaces; + +public interface IProjectTagRepository +{ + Task SetTags(string projectKey, string[] tags); +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Repositories/PermissionRepository.cs b/ConsoleApps/SonarClient/SonarClient/Repositories/PermissionRepository.cs new file mode 100644 index 0000000..acb84d2 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Repositories/PermissionRepository.cs @@ -0,0 +1,28 @@ +using SonarClient.Constants; +using SonarClient.Models; +using SonarClient.Repositories.Interfaces; +using System.Net.Http.Json; + +namespace SonarClient.Repositories; + +public class PermissionRepository(IHttpClientFactory httpClientFactory) : IPermissionRepository +{ + private readonly HttpClient _httpClient = httpClientFactory.CreateClient(SonarConstants.SonarApiV1ClientName); + + public async Task> GetPermissionTemplates(string organizationKey) + { + var permissionTemplateResponse = await _httpClient.GetFromJsonAsync($"/api/permissions/search_templates?organization={organizationKey}"); + return permissionTemplateResponse.PermissionTemplates; + } + + public async Task ApplyTemplateToProject(string projectKey, string templateId) + { + var content = new FormUrlEncodedContent([ + new KeyValuePair("projectKey", projectKey), + new KeyValuePair("templateId", templateId) + ]); + + var response = await _httpClient.PostAsync("api/permissions/apply_template", content); + response.EnsureSuccessStatusCode(); + } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Repositories/ProjectRepository.cs b/ConsoleApps/SonarClient/SonarClient/Repositories/ProjectRepository.cs new file mode 100644 index 0000000..7170edb --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Repositories/ProjectRepository.cs @@ -0,0 +1,34 @@ +using SonarClient.Constants; +using SonarClient.Models; +using SonarClient.Repositories.Interfaces; +using System.Net.Http.Json; + +namespace SonarClient.Repositories; + +public class ProjectRepository(IHttpClientFactory httpClientFactory) : IProjectRepository +{ + private readonly HttpClient _httpClient = httpClientFactory.CreateClient(SonarConstants.SonarApiV2ClientName); + + public async Task> GetProjects(string organizationId) + { + var projects = new List(); + var currentPage = 1; + ProjectsSearchResponse projectResponse; + + do + { + projectResponse = await GetProjects(organizationId, currentPage); + projects.AddRange(projectResponse.Projects); + currentPage++; + } while (projects.Count < projectResponse.Page.Total); + + return projects; + } + + private async Task GetProjects(string organizationId, int page) + { + return await _httpClient.GetFromJsonAsync( + $"/projects/projects?organizationIds={organizationId}&pageIndex={page}" + ) ?? throw new InvalidOperationException("Failed to retrieve projects from SonarCloud"); + } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/Repositories/ProjectTagRepository.cs b/ConsoleApps/SonarClient/SonarClient/Repositories/ProjectTagRepository.cs new file mode 100644 index 0000000..be5816e --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/Repositories/ProjectTagRepository.cs @@ -0,0 +1,22 @@ +using SonarClient.Constants; +using SonarClient.Repositories.Interfaces; + +namespace SonarClient.Repositories; + +public class ProjectTagRepository(IHttpClientFactory httpClientFactory) : IProjectTagRepository +{ + private readonly HttpClient _httpClient = httpClientFactory.CreateClient(SonarConstants.SonarApiV1ClientName); + + public async Task SetTags(string projectKey, string[] tags) + { + var tagsCommaSeperated = string.Join(",", tags); + + var content = new FormUrlEncodedContent([ + new KeyValuePair("project", projectKey), + new KeyValuePair("tags", tagsCommaSeperated) + ]); + + var response = await _httpClient.PostAsync("api/project_tags/set", content); + response.EnsureSuccessStatusCode(); + } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/SonarClient.csproj b/ConsoleApps/SonarClient/SonarClient/SonarClient.csproj new file mode 100644 index 0000000..d2298c5 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/SonarClient.csproj @@ -0,0 +1,24 @@ + + + + Exe + net8.0 + enable + disable + e28ed066-de2c-46f8-865f-4b0eda67be3c + + + + + + + + + + + + Always + + + + diff --git a/ConsoleApps/SonarClient/SonarClient/appsettings.json b/ConsoleApps/SonarClient/SonarClient/appsettings.json new file mode 100644 index 0000000..7849cc3 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/appsettings.json @@ -0,0 +1,7 @@ +{ + "sonar": { + "api_key": "", + "organization_id": "aa1160b9-e4ab-4d2a-9ce5-bac8d9b5fb06", + "organization_key": "effectory" + } +} \ No newline at end of file diff --git a/ConsoleApps/SonarClient/SonarClient/sonar-client.md b/ConsoleApps/SonarClient/SonarClient/sonar-client.md new file mode 100644 index 0000000..1e56fe1 --- /dev/null +++ b/ConsoleApps/SonarClient/SonarClient/sonar-client.md @@ -0,0 +1,44 @@ +# SonarClient Console Application + +## Overview + +SonarClient is a console application designed to interact with SonarQube/SonarCloud REST APIs. This tool +provides functionality to manage, query, and analyze code quality metrics and security findings from SonarQube +instances. + +Depending on the operations, the application will reach out to v1 or v2 of the SonarQube API. + +Documentation for the v1 API can be found here: https://sonarcloud.io/web_api +Documentation for the v2 API can be found here: https://api-docs.sonarsource.com/ + +## Features + +- **Update Permissions And Tags**: Update permissions and tags for all projects. Added for migration to new team structure. + +## Operations + +- Retrieve all projects +- Set tags for a project +- Apply permission template on a project + +## Usage + +1. Retrieve a personal token from SonarCloud: + - Go to: https://sonarcloud.io/account/security + - Enter token name + - Click `Generate Token` + - Copy token +2. In Rider + - Right-click on the `SonarClient` project + - Select `Tools > .NET User Secrets` + - This will open `secrets.json` + - Add the following: + ```json + { + "sonar": { + "api_key": "[add token here]" + } + }` +3. Run the application +4. When done revoke the token + diff --git a/_azuredevops/cloud-engineering.yml b/_azuredevops/cloud-engineering.yml new file mode 100644 index 0000000..dd8b1dc --- /dev/null +++ b/_azuredevops/cloud-engineering.yml @@ -0,0 +1,47 @@ +trigger: + branches: + include: + - master +pool: + vmImage: 'windows-latest' + +variables: + repositoryName: $(Build.Repository.Name) + buildConfiguration: release + +stages: + - stage: build + displayName: 'Build for branch policy validation' + jobs: + - job: build + displayName: 'Build solution' + steps: + - task: SonarCloudPrepare@3 + inputs: + SonarCloud: SonarCloud + organization: effectory + scannerMode: dotnet + projectKey: platform-cloud-engineering + projectName: repositoryName + extraProperties: | + sonar.exclusions=**/obj/**,**/*.dll + sonar.cs.vstest.reportsPaths=$(Agent.TempDirectory)/**/*.trx + sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/**/coverage.opencover.xml + sonar.coverage.exclusions=**/* + sonar.scm.enabled=true + sonar.scm.provider=git + sonar.pullrequest.provider=vsts + + - task: DotNetCoreCLI@2 + displayName: 'dotnet build console apps' + inputs: + command: build + projects: | + ConsoleApps/**/*.csproj + arguments: '-c $(buildConfiguration)' + + - task: SonarCloudAnalyze@3 + + - task: SonarCloudPublish@3 + inputs: + pollingTimeoutSec: '300'