diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7746dabf..80190c41e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,9 @@ name: .NET on: pull_request: branches: [ "master" ] + push: + branches: [ "master" ] + jobs: build: @@ -15,7 +18,5 @@ jobs: uses: actions/setup-dotnet@v2 with: dotnet-version: 6.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore + - name: Run nuke build + run: ./build.ps1 diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json new file mode 100644 index 000000000..607f91310 --- /dev/null +++ b/.nuke/build.schema.json @@ -0,0 +1,118 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/build", + "title": "Build Schema", + "definitions": { + "build": { + "type": "object", + "properties": { + "Configuration": { + "type": "string", + "enum": [ + "Debug", + "Release" + ] + }, + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "type": "string", + "description": "Host for execution. Default is 'automatic'", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "Information", + "Preparation", + "Restore", + "RunUnitTests" + ] + } + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "Information", + "Preparation", + "Restore", + "RunUnitTests" + ] + } + }, + "TestResultDirectory": { + "type": "string" + }, + "Verbosity": { + "type": "string", + "description": "Logging verbosity during build execution. Default is 'Normal'", + "enum": [ + "Minimal", + "Normal", + "Quiet", + "Verbose" + ] + } + } + } + } +} diff --git a/.nuke/gitignore.txt b/.nuke/gitignore.txt new file mode 100644 index 000000000..abe251a76 --- /dev/null +++ b/.nuke/gitignore.txt @@ -0,0 +1 @@ +/temp \ No newline at end of file diff --git a/.nuke/parameters.json b/.nuke/parameters.json new file mode 100644 index 000000000..64f5db5dc --- /dev/null +++ b/.nuke/parameters.json @@ -0,0 +1,4 @@ +{ + "$schema": "./build.schema.json", + "Solution": "TaskoMask.sln" +} diff --git a/TaskoMask.sln b/TaskoMask.sln index 53ecc5537..7605602e1 100644 --- a/TaskoMask.sln +++ b/TaskoMask.sln @@ -45,7 +45,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "src\3-ApiGate EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aggregator", "src\3-ApiGateways\UserPanel\Aggregator\Aggregator.csproj", "{7ED1857A-DF8A-4289-95CC-D8943BBEB345}" EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "src\6-Docker\docker-compose.dcproj", "{1D9C2F8D-A17E-476C-B458-7F9C046F5A25}" +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "src\7-Docker\docker-compose.dcproj", "{1D9C2F8D-A17E-476C-B458-7F9C046F5A25}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{3C1EE73E-D1FB-4AA8-A6C8-3D999236CCCB}" EndProject @@ -115,6 +115,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Owners.Write.Tests.Base", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tasks.Write.Tests.Base", "src\2-Services\Tasks\Tests\Tasks.Write.Tests.Base\Tasks.Write.Tests.Base.csproj", "{CD0FA341-0147-4FCC-91BE-D5C855EBB000}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Build", "src\6-Build\Build.csproj", "{DF42E4C4-A08C-452D-8E6F-6D5ED4FDDEB6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6-Build", "6-Build", "{EA254FA1-9AC4-4258-B408-1E6C7EE073A7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -269,6 +273,8 @@ Global {CD0FA341-0147-4FCC-91BE-D5C855EBB000}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD0FA341-0147-4FCC-91BE-D5C855EBB000}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD0FA341-0147-4FCC-91BE-D5C855EBB000}.Release|Any CPU.Build.0 = Release|Any CPU + {DF42E4C4-A08C-452D-8E6F-6D5ED4FDDEB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF42E4C4-A08C-452D-8E6F-6D5ED4FDDEB6}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -324,6 +330,7 @@ Global {23E52127-118C-4ADF-8C5B-59F0C31AE5EC} = {D1E5A47A-4F71-49D3-895F-326C6557D34A} {DF4B4D2C-B9F2-4B9A-94E9-FC2B0BF645DB} = {A12B4AA2-F3E5-4B3D-B6A4-395117D5CE61} {CD0FA341-0147-4FCC-91BE-D5C855EBB000} = {19E5AA58-D87A-4117-B9A7-A281B793D14D} + {DF42E4C4-A08C-452D-8E6F-6D5ED4FDDEB6} = {EA254FA1-9AC4-4258-B408-1E6C7EE073A7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {15E4B299-A968-40A3-8192-CCF9082EE3BC} diff --git a/build.cmd b/build.cmd new file mode 100755 index 000000000..b08cc590f --- /dev/null +++ b/build.cmd @@ -0,0 +1,7 @@ +:; set -eo pipefail +:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) +:; ${SCRIPT_DIR}/build.sh "$@" +:; exit $? + +@ECHO OFF +powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %* diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 000000000..7463addb3 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,74 @@ +[CmdletBinding()] +Param( + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$BuildArguments +) + +Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" + +Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } +$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent + +########################################################################### +# CONFIGURATION +########################################################################### + +$BuildProjectFile = "$PSScriptRoot\src\6-Build\Build.csproj" +$TempDirectory = "$PSScriptRoot\\.nuke\temp" + +$DotNetGlobalFile = "$PSScriptRoot\\global.json" +$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" +$DotNetChannel = "STS" + +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 +$env:DOTNET_MULTILEVEL_LOOKUP = 0 + +########################################################################### +# EXECUTION +########################################################################### + +function ExecSafe([scriptblock] $cmd) { + & $cmd + if ($LASTEXITCODE) { exit $LASTEXITCODE } +} + +# If dotnet CLI is installed globally and it matches requested version, use for execution +if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` + $(dotnet --version) -and $LASTEXITCODE -eq 0) { + $env:DOTNET_EXE = (Get-Command "dotnet").Path +} +else { + # Download install script + $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" + New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) + + # If global.json exists, load expected version + if (Test-Path $DotNetGlobalFile) { + $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) + if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { + $DotNetVersion = $DotNetGlobal.sdk.version + } + } + + # Install by channel or version + $DotNetDirectory = "$TempDirectory\dotnet-win" + if (!(Test-Path variable:DotNetVersion)) { + ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } + } else { + ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } + } + $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" +} + +Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" + +if (Test-Path env:NUKE_ENTERPRISE_TOKEN) { + & $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null + & $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null +} + +ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } +ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..63598fe8a --- /dev/null +++ b/build.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +bash --version 2>&1 | head -n 1 + +set -eo pipefail +SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) + +########################################################################### +# CONFIGURATION +########################################################################### + +BUILD_PROJECT_FILE="$SCRIPT_DIR/src/6-Build/Build.csproj" +TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp" + +DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" +DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" +DOTNET_CHANNEL="STS" + +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export DOTNET_MULTILEVEL_LOOKUP=0 + +########################################################################### +# EXECUTION +########################################################################### + +function FirstJsonValue { + perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" +} + +# If dotnet CLI is installed globally and it matches requested version, use for execution +if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then + export DOTNET_EXE="$(command -v dotnet)" +else + # Download install script + DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" + mkdir -p "$TEMP_DIRECTORY" + curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" + chmod +x "$DOTNET_INSTALL_FILE" + + # If global.json exists, load expected version + if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then + DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")") + if [[ "$DOTNET_VERSION" == "" ]]; then + unset DOTNET_VERSION + fi + fi + + # Install by channel or version + DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" + if [[ -z ${DOTNET_VERSION+x} ]]; then + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path + else + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path + fi + export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" +fi + +echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)" + +if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then + "$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true + "$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true +fi + +"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet +"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" diff --git a/src/6-Build/Build.cs b/src/6-Build/Build.cs new file mode 100644 index 000000000..e40848270 --- /dev/null +++ b/src/6-Build/Build.cs @@ -0,0 +1,94 @@ +using Serilog; +using System.Linq; +using Nuke.Common; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.Coverlet; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Tools.GitVersion; +using Nuke.Common.Tools.NuGet; +using static Nuke.Common.EnvironmentInfo; +using static Nuke.Common.IO.FileSystemTasks; +using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.DotNet.DotNetTasks; +using static Nuke.Common.Tools.NuGet.NuGetTasks; +using static Nuke.Common.Tools.NuGet.NuGetPackSettingsExtensions; +using Nuke.Common.IO; +using Nuke.Common.Utilities.Collections; + +class Build : NukeBuild +{ + public static int Main() => Execute(x => x.RunUnitTests); + + [Parameter] + readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; + + [Solution] + readonly Solution Solution; + + [Parameter] + AbsolutePath TestResultDirectory = RootDirectory + "/Artifacts/Test-Results/"; + + Target Information => _ => _ + .Before(Preparation) + .Executes(() => + { + Log.Information($"Solution path : {Solution}"); + Log.Information($"Solution directory : {Solution.Directory}"); + Log.Information($"Configuration : {Configuration}"); + Log.Information($"TestResultDirectory : {TestResultDirectory}"); + }); + + Target Preparation => _ => _ + .DependsOn(Information) + .Executes(() => + { + TestResultDirectory.CreateOrCleanDirectory(); + }); + + Target Clean => _ => _ + .DependsOn(Preparation) + .Executes(() => + { + DotNetClean(); + }); + + Target Restore => _ => _ + .DependsOn(Clean) + .Executes(() => + { + DotNetRestore(a => a.SetProjectFile(Solution)); + }); + + Target Compile => _ => _ + .DependsOn(Restore) + .Executes(() => + { + DotNetBuild(a => + a.SetProjectFile(Solution) + .SetNoRestore(true) + .SetConfiguration(Configuration)); + }); + + Target RunUnitTests => _ => _ + .DependsOn(Compile) + .Executes(() => + { + var testProjects = Solution.AllProjects.Where(s => s.Name.EndsWith("Tests.Unit")); + + DotNetTest(a => a + .SetConfiguration(Configuration) + .SetNoBuild(true) + .SetNoRestore(true) + .ResetVerbosity() + .SetResultsDirectory(TestResultDirectory) + .EnableCollectCoverage() + .SetCoverletOutputFormat(CoverletOutputFormat.opencover) + .SetExcludeByFile("*.Generated.cs") + .EnableUseSourceLink() + .CombineWith(testProjects, (b, z) => b + .SetProjectFile(z) + .AddLoggers($"trx;LogFileName={z.Name}.trx") + .SetCoverletOutput(TestResultDirectory + $"{z.Name}.xml"))); + }); +} diff --git a/src/6-Build/Build.csproj b/src/6-Build/Build.csproj new file mode 100644 index 000000000..71f0c06d8 --- /dev/null +++ b/src/6-Build/Build.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + TaskoMask.Build + TaskoMask.Build + CS0649;CS0169;CA1050;CA1822;CA2211;IDE1006 + 1 + false + + + + + + + + diff --git a/src/6-Build/Build.csproj.DotSettings b/src/6-Build/Build.csproj.DotSettings new file mode 100644 index 000000000..a778f33d9 --- /dev/null +++ b/src/6-Build/Build.csproj.DotSettings @@ -0,0 +1,27 @@ + + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + Implicit + Implicit + ExpressionBody + 0 + NEXT_LINE + True + False + 120 + IF_OWNER_IS_SINGLE_LINE + WRAP_IF_LONG + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True diff --git a/src/6-Build/Configuration.cs b/src/6-Build/Configuration.cs new file mode 100644 index 000000000..9c08b1ae9 --- /dev/null +++ b/src/6-Build/Configuration.cs @@ -0,0 +1,16 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Nuke.Common.Tooling; + +[TypeConverter(typeof(TypeConverter))] +public class Configuration : Enumeration +{ + public static Configuration Debug = new Configuration { Value = nameof(Debug) }; + public static Configuration Release = new Configuration { Value = nameof(Release) }; + + public static implicit operator string(Configuration configuration) + { + return configuration.Value; + } +} diff --git a/src/6-Build/Directory.Build.props b/src/6-Build/Directory.Build.props new file mode 100644 index 000000000..e147d6352 --- /dev/null +++ b/src/6-Build/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/6-Build/Directory.Build.targets b/src/6-Build/Directory.Build.targets new file mode 100644 index 000000000..253260956 --- /dev/null +++ b/src/6-Build/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/6-Docker/.dockerignore b/src/7-Docker/.dockerignore similarity index 100% rename from src/6-Docker/.dockerignore rename to src/7-Docker/.dockerignore diff --git a/src/6-Docker/Infrastructure.yml b/src/7-Docker/Infrastructure.yml similarity index 100% rename from src/6-Docker/Infrastructure.yml rename to src/7-Docker/Infrastructure.yml diff --git a/src/6-Docker/docker-compose.dcproj b/src/7-Docker/docker-compose.dcproj similarity index 100% rename from src/6-Docker/docker-compose.dcproj rename to src/7-Docker/docker-compose.dcproj diff --git a/src/6-Docker/docker-compose.override.yml b/src/7-Docker/docker-compose.override.yml similarity index 99% rename from src/6-Docker/docker-compose.override.yml rename to src/7-Docker/docker-compose.override.yml index 455c297b5..f0149605f 100644 --- a/src/6-Docker/docker-compose.override.yml +++ b/src/7-Docker/docker-compose.override.yml @@ -27,7 +27,7 @@ services: - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro - owners.write.api: + owners-write-api: environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=https://+:443;http://+:80 diff --git a/src/6-Docker/docker-compose.yml b/src/7-Docker/docker-compose.yml similarity index 99% rename from src/6-Docker/docker-compose.yml rename to src/7-Docker/docker-compose.yml index d1ebe3d4b..4a6d11c70 100644 --- a/src/6-Docker/docker-compose.yml +++ b/src/7-Docker/docker-compose.yml @@ -120,7 +120,7 @@ services: RabbitMQ__Host: "rabbitmq" build: context: ../.. - dockerfile: src/2-Services/Owners/Read/Api/Owners.Write.Api/Dockerfile + dockerfile: src/2-Services/Owners/Read/Api/Owners.Read.Api/Dockerfile ports: - 5020:80 - 5021:443 diff --git a/src/6-Docker/launchSettings.json b/src/7-Docker/launchSettings.json similarity index 100% rename from src/6-Docker/launchSettings.json rename to src/7-Docker/launchSettings.json