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'