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'