diff --git a/.github/actions/analyze.yml b/.github/actions/analyze.yml new file mode 100644 index 0000000000..5ad0110a60 --- /dev/null +++ b/.github/actions/analyze.yml @@ -0,0 +1,201 @@ +parameters: + analyzeARMTemplates: true + analyzeBinaries: true + analyzePackages: true + runAntiMalware: true + credScanDirectory: '$(Build.SourcesDirectory)' + +steps: +- task: UseDotNet@2 + displayName: Use .NET Core sdk + inputs: + useGlobalJson: true + +- ${{ if eq(parameters.analyzeBinaries, 'true') }}: + - task: DownloadBuildArtifacts@0 + displayName: 'Download Binaries' + inputs: + buildType: 'current' + downloadType: 'single' + downloadPath: '$(Agent.TempDirectory)/artifacts' + artifactName: 'deploy' + +- ${{ if eq(parameters.analyzePackages, 'true') }}: + - task: DownloadBuildArtifacts@0 + displayName: 'Download NuGet Packages' + inputs: + buildType: 'current' + downloadType: 'single' + downloadPath: '$(Build.SourcesDirectory)/artifacts' + artifactName: 'nuget' + +- ${{ if eq(parameters.analyzeBinaries, 'true') }}: + - task: ExtractFiles@1 + displayName: 'Extract Stu3 Web Server Binaries' + inputs: + archiveFilePatterns: '$(Agent.TempDirectory)/artifacts/deploy/Microsoft.Health.Fhir.Stu3.Web.zip' + destinationFolder: '$(Build.SourcesDirectory)/artifacts/web/Stu3' + - task: ExtractFiles@1 + displayName: 'Extract R4 Web Server Binaries' + inputs: + archiveFilePatterns: '$(Agent.TempDirectory)/artifacts/deploy/Microsoft.Health.Fhir.R4.Web.zip' + destinationFolder: '$(Build.SourcesDirectory)/artifacts/web/r4' + - task: ExtractFiles@1 + displayName: 'Extract R4B Web Server Binaries' + inputs: + archiveFilePatterns: '$(Agent.TempDirectory)/artifacts/deploy/Microsoft.Health.Fhir.R4B.Web.zip' + destinationFolder: '$(Build.SourcesDirectory)/artifacts/web/r4b' + - task: ExtractFiles@1 + displayName: 'Extract R5 Web Server Binaries' + inputs: + archiveFilePatterns: '$(Agent.TempDirectory)/artifacts/deploy/Microsoft.Health.Fhir.R5.Web.zip' + destinationFolder: '$(Build.SourcesDirectory)/artifacts/web/r5' + +- ${{ if eq(parameters.runAntiMalware, 'true') }}: + - task: AntiMalware@4 + inputs: + InputType: 'Basic' + ScanType: 'CustomScan' + FileDirPath: '$(Build.SourcesDirectory)' + EnableServices: true + TreatSignatureUpdateFailureAs: 'Standard' + SignatureFreshness: 'OneDay' + TreatStaleSignatureAs: 'Error' + +- ${{ if eq(parameters.analyzeARMTemplates, 'true') }}: + - task: Armory@2 + inputs: + targetDirectory: '$(Build.SourcesDirectory)/samples/templates' + targetFiles: 'f|*.json' + excludePassesFromLog: false + + - task: TemplateAnalyzer@3 + displayName: 'Run Template Analyzer' + inputs: + ToolVersion: Latest + AnalyzeDirectory: '$(Build.SourcesDirectory)/samples/templates' + Verbose: false + IncludeNonSecurityRules: true + +- task: CredScan@3 + inputs: + scanFolder: ${{ parameters.credScanDirectory }} + outputFormat: 'pre' + suppressionsFile: 'CredScanSuppressions.json' + verboseOutput: true + +- task: CSRF@1 + inputs: + Path: '$(Build.SourcesDirectory)' + ToolVersion: Latest + +- task: Trivy@1 + displayName: 'Run Trivy' + inputs: + Target: '$(Build.SourcesDirectory)/build/docker' + Severities: all + VulTypes: all + +- task: PSScriptAnalyzer@1 + displayName: 'Run PSScriptAnalyzer' + inputs: + Path: '$(Build.SourcesDirectory)' + Settings: required + IgnorePattern: .gdn + Recurse: true + +- task: RoslynAnalyzers@3 + inputs: + userProvideBuildInfo: 'msBuildInfo' + msBuildArchitecture: 'DotNetCore' + msBuildCommandline: 'dotnet build $(Build.SourcesDirectory)/Microsoft.Health.Fhir.sln --configuration $(buildConfiguration) -p:ContinuousIntegrationBuild=true -f net8.0' + +- task: BinSkim@4 + inputs: + toolVersion: Latest + InputType: Basic + Function: analyze + AnalyzeTargetGlob: 'f|$(Agent.TempDirectory)/artifacts/**/*Microsoft.Health.*.dll' + + ## PoliCheck@2 does not need to be added since it is run internally + + ## Tools that are no longer supported: + # AutoApplicability@1, CodeMetrics@1, VulnerabilityAssessment@0 + +- task: SdtReport@2 + condition: succeededOrFailed() + continueOnError: True + inputs: + GdnExportAllTools: false + GdnExportGdnToolArmory: ${{ eq(parameters.analyzeARMTemplates, 'true') }} + GdnExportGdnToolCredScan: true + GdnExportGdnToolCSRF: true + GdnExportGdnToolRoslynAnalyzers: true + BinSkim: true + CredScan: true + +- task: PublishSecurityAnalysisLogs@3 + condition: succeededOrFailed() + continueOnError: True + inputs: + ArtifactName: 'CodeAnalysisLogs' + ArtifactType: 'Container' + AllTools: false + AntiMalware: ${{ eq(parameters.runAntiMalware, 'true') }} + APIScan: false + Armory: ${{ eq(parameters.analyzeARMTemplates, 'true') }} + Bandit: false + BinSkim: false + CodesignValidation: false + CredScan: true + CSRF: true + ESLint: false + Flawfinder: false + FortifySCA: false + FxCop: false + ModernCop: false + MSRD: false + PoliCheck: false + RoslynAnalyzers: true + SDLNativeRules: false + Semmle: false + SpotBugs: false + TSLint: false + WebScout: false + ToolLogsNotFoundAction: 'Standard' + +- task: PostAnalysis@2 + condition: succeededOrFailed() + inputs: + GdnBreakAllTools: false + GdnBreakGdnToolArmory: ${{ eq(parameters.analyzeARMTemplates, 'true') }} + GdnBreakGdnToolCredScan: true + GdnBreakGdnToolCSRF: true + GdnBreakGdnToolRoslynAnalyzers: true + BinSkim: true + CredScan: true + +- task: TSAUpload@2 + condition: and(succeeded(), eq(variables['build.sourceBranch'], 'refs/heads/main')) + displayName: 'TSA upload' + inputs: + tsaVersion: 'TsaV2' + codebase: 'NewOrUpdate' + GdnPublishTsaOnboard: false + GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\build\jobs\tsaconfig.gdntsa' + GdnPublishTsaExportedResultsPublishable: true + +- task: DeleteFiles@1 + displayName: 'Delete files to make space' + inputs: + SourceFolder: '$(Build.SourcesDirectory)' + Contents: '**\*' + +- task: DropValidatorTask@0 + displayName: 'SBOM Validator and Publisher Task' + inputs: + BuildDropPath: '$(Agent.TempDirectory)/artifacts/deploy' + OutputPath: 'output.json' + ValidateSignature: true + Verbosity: 'Verbose' + continueOnError: true diff --git a/.github/actions/build.yml b/.github/actions/build.yml new file mode 100644 index 0000000000..8bb12db04d --- /dev/null +++ b/.github/actions/build.yml @@ -0,0 +1,88 @@ +parameters: + # Default values + unitTest: true + codeCoverage: false + componentGovernance: false + packageArtifacts: false + packageIntegrationTests: false + targetBuildFramework: '' + +steps: +- task: UseDotNet@2 + displayName: 'Use .NET SDK' + inputs: + useGlobalJson: true + +- ${{ if eq(parameters.targetBuildFramework, '') }}: + - task: DotNetCoreCLI@2 + displayName: 'dotnet build $(buildConfiguration)' + inputs: + command: build + arguments: '--configuration $(buildConfiguration) -p:ContinuousIntegrationBuild=true -p:AssemblyVersion="$(assemblySemVer)" -p:FileVersion="$(assemblySemFileVer)" -p:InformationalVersion="$(informationalVersion)" -p:Version="$(majorMinorPatch)" -warnaserror' + workingDirectory: $(System.DefaultWorkingDirectory) + +- ${{ if ne(parameters.targetBuildFramework, '') }}: + - task: DotNetCoreCLI@2 + displayName: 'dotnet build $(buildConfiguration)' + inputs: + command: build + arguments: '--configuration $(buildConfiguration) -p:ContinuousIntegrationBuild=true -p:AssemblyVersion="$(assemblySemVer)" -p:FileVersion="$(assemblySemFileVer)" -p:InformationalVersion="$(informationalVersion)" -p:Version="$(majorMinorPatch)" -warnaserror -f ${{parameters.targetBuildFramework}}' + workingDirectory: $(System.DefaultWorkingDirectory) + +- ${{ if eq(parameters.unitTest, 'true') }}: + - task: DotNetCoreCLI@2 + displayName: 'dotnet test' + inputs: + command: test + projects: '**/*UnitTests/*.csproj' + arguments: '--configuration $(buildConfiguration) --no-build -f ${{parameters.targetBuildFramework}}' + testRunTitle: 'Unit Tests' + +- ${{ if eq(parameters.codeCoverage, 'true') }}: + - task: DotNetCoreCLI@2 + displayName: 'dotnet test with coverage' + inputs: + command: test + projects: '**/*UnitTests/*.csproj' + arguments: '--configuration $(buildConfiguration) --no-build --collect "XPlat Code Coverage" -s "$(build.sourcesDirectory)/CodeCoverage.runsettings" -v normal -f ${{parameters.targetBuildFramework}}' + testRunTitle: 'Unit Tests' + - task: reportgenerator@5 + displayName: 'aggregate code coverage' + condition: succeededOrFailed() + inputs: + reports: '$(Agent.TempDirectory)/*/coverage.cobertura.xml' + reporttypes: 'Cobertura' + targetdir: '$(Agent.TempDirectory)/coverage' + - task: PublishCodeCoverageResults@1 + displayName: 'publish code coverage' + condition: succeededOrFailed() + inputs: + codeCoverageTool: 'Cobertura' + failIfCoverageEmpty: true + summaryFileLocation: '$(Agent.TempDirectory)/coverage/Cobertura.xml' + - task: PublishBuildArtifacts@1 + displayName: 'publish Cobertura.xml' + inputs: + pathToPublish: '$(Agent.TempDirectory)/coverage/Cobertura.xml' + artifactName: 'IntegrationTests' + artifactType: 'container' + +- ${{ if eq(parameters.packageArtifacts, 'true') }}: + # https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/secure-supply-chain/ado-sbom-generator + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'SBOM Generation Task' + inputs: + BuildDropPath: '$(build.artifactStagingDirectory)' + BuildComponentPath: '$(Build.SourcesDirectory)' + - task: PublishBuildArtifacts@1 + displayName: 'Publish SBOM Artifacts' + inputs: + pathToPublish: '$(build.artifactStagingDirectory)' + artifactName: 'deploy' + artifactType: 'container' + +- ${{ if eq(parameters.packageArtifacts, 'true') }}: + - template: package.yml + +- ${{ if eq(parameters.packageIntegrationTests, 'true') }}: + - template: package-integration-tests.yml diff --git a/.github/actions/clean-storage-accounts/action.yml b/.github/actions/clean-storage-accounts/action.yml new file mode 100644 index 0000000000..35df072a4c --- /dev/null +++ b/.github/actions/clean-storage-accounts/action.yml @@ -0,0 +1,30 @@ +name: clean storage Accounts +description: Removes blob containers from test storage accounts + +inputs: + environmentName: + description: Deployment environment name + required: true + +runs: + using: 'composite' + steps: + - name: Clean Storage Accounts + uses: azure/powershell@v2 + with: + azPSVersion: "latest" + inlineScript: | + $currentUtcTime = [DateTime]::UtcNow + Get-AzContext + $storageAccounts = Get-AzStorageAccount -ResourceGroupName ${{ inputs.environmentName }} + foreach ($storageAccount in $storageAccounts) { + + $storageContainers = Get-AzStorageContainer -Name * -Context $storageAccount.Context + foreach ($container in $storageContainers) { + $ageDiff = $currentUtcTime - $container.CloudBlobContainer.Properties.LastModified.UtcDateTime + if($ageDiff.TotalDays -ge 3) { + Write-Host "Deleting container $($container.Name)" + $container.CloudBlobContainer.Delete() + } + } + } diff --git a/.github/actions/cleanup-integration-test-databases/action.yml b/.github/actions/cleanup-integration-test-databases/action.yml new file mode 100644 index 0000000000..993c861dfb --- /dev/null +++ b/.github/actions/cleanup-integration-test-databases/action.yml @@ -0,0 +1,25 @@ +name: cleanup integration test databases +description: Deletes databases used for integration tests from previous runs + +inputs: + environmentName: + description: Deployment environment name + required: true + +runs: + using: 'composite' + steps: + - name: Remove Integration Test Databases + uses: azure/powershell@v1 + with: + azPSVersion: "latest" + inlineScript: | + Get-AzContext + $testNamePatterns = @("SNAPSHOT*","FHIRCOMPATIBILITYTEST*","FHIRINTEGRATIONTEST*","FHIRRESOURCECHANGEDISABLEDTEST*","BASE*","SNAPSHOT*") + foreach ($pattern in $testNamePatterns) { + $resources = Get-AzResource -ResourceGroupName ${{ inputs.environmentName }} -ResourceType 'Microsoft.Sql/servers/databases' -Name $pattern + foreach ($resource in $resources) { + Write-Host "Cleaning up $($resource.ResourceName)" + Remove-AzResource -ResourceId $resource.ResourceId -Force + } + } diff --git a/.github/actions/docker-add-tag.yml b/.github/actions/docker-add-tag.yml new file mode 100644 index 0000000000..031ae5e484 --- /dev/null +++ b/.github/actions/docker-add-tag.yml @@ -0,0 +1,28 @@ + +parameters: +- name: sourceTag + type: string +- name: targetTag + type: string + +jobs: +- job: DockerAddTag + pool: + name: '$(DefaultLinuxPool)' + vmImage: '$(LinuxVmImage)' + steps: + - task: AzureCLI@2 + displayName: 'Azure CLI: InlineScript' + inputs: + azureSubscription: $(ConnectedServiceName) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + az acr login -n $(azureContainerRegistry) + for v in stu3 r4 r4b r5; do + sourceImage="$(azureContainerRegistry)/${v}_fhir-server:${{parameters.sourceTag}}" + targetImage="$(azureContainerRegistry)/${v}_fhir-server:${{parameters.targetTag}}" + docker pull $sourceImage + docker tag $sourceImage $targetImage + docker push $targetImage + done diff --git a/.github/actions/docker-add-tag/action.yml b/.github/actions/docker-add-tag/action.yml new file mode 100644 index 0000000000..68d89b1835 --- /dev/null +++ b/.github/actions/docker-add-tag/action.yml @@ -0,0 +1,36 @@ +name: Docker Add Main tag +description: 'Adds the main tag to the images for all supported FHIR versions' + +inputs: + sourceTag: + description: 'The tag to apply to the images' + required: true + targetTag: + description: 'The tag to apply to the images' + required: true + fhirSchemaVersion: + description: 'The FHIR schema version to package' + required: true + azureContainerRegistry: + description: 'The Azure Container Registry to push the images to' + required: true + +runs: + using: 'composite' + steps: + - name: Azure Login + uses: azure/login@v2 + with: + client-id: ${{secrets.AZURE_CLIENT_ID}} + subscription-id: ${{secrets.AZURE_SUBSCRIPTION_ID}} + tenant-id: ${{secrets.AZURE_TENANT_ID}} + enable-AzPSSession: true + - name: Add Tag to Docker Images + shell: bash + run: | + az acr login -n ${{inputs.azureContainerRegistry}} + sourceImage="${{inputs.azureContainerRegistry}}/${{inputs.fhirSchemaVersion}}_fhir-server:${{inputs.sourceTag}}" + targetImage="${{inputs.azureContainerRegistry}}/${{inputs.fhirSchemaVersion}}_fhir-server:${{inputs.targetTag}}" + docker pull $sourceImage + docker tag $sourceImage $targetImage + docker push $targetImage diff --git a/.github/actions/docker-build-all.yml b/.github/actions/docker-build-all.yml new file mode 100644 index 0000000000..3f6e766c53 --- /dev/null +++ b/.github/actions/docker-build-all.yml @@ -0,0 +1,27 @@ +# DESCRIPTION: +# Builds and pushes images for all supported FHIR versions + +parameters: +- name: tag + type: string + +jobs: +- template: docker-build-push.yml + parameters: + version: "R4" + tag: ${{parameters.tag}} + +- template: docker-build-push.yml + parameters: + version: "R4B" + tag: ${{parameters.tag}} + +- template: docker-build-push.yml + parameters: + version: "Stu3" + tag: ${{parameters.tag}} + +- template: docker-build-push.yml + parameters: + version: "R5" + tag: ${{parameters.tag}} diff --git a/.github/actions/docker-build-push.yml b/.github/actions/docker-build-push.yml new file mode 100644 index 0000000000..77cacb2f94 --- /dev/null +++ b/.github/actions/docker-build-push.yml @@ -0,0 +1,40 @@ +# DESCRIPTION: +# Builds and pushes a docker image for a given FHIR version + +parameters: +- name: version + type: string +- name: tag + type: string + +jobs: +- job: '${{parameters.version}}_Docker' + pool: + name: '$(DefaultLinuxPool)' + vmImage: '$(LinuxVmImage)' + steps: + - task: DockerCompose@0 + displayName: 'Build FHIR ${{parameters.version}} Server Image' + inputs: + action: Build services + azureSubscriptionEndpoint: $(azureSubscriptionEndpoint) + azureContainerRegistry: $(azureContainerRegistry) + dockerComposeFile: $(composeLocation) + dockerComposeFileArgs: | + FHIR_VERSION=${{parameters.version}} + ASSEMBLY_VER=$(assemblySemFileVer) + projectName: ${{parameters.version}} + additionalImageTags: ${{parameters.tag}} + + - task: DockerCompose@0 + displayName: 'Push FHIR ${{parameters.version}} Server Image' + inputs: + action: Push services + azureSubscriptionEndpoint: $(azureSubscriptionEndpoint) + azureContainerRegistry: $(azureContainerRegistry) + dockerComposeFile: $(composeLocation) + dockerComposeFileArgs: | + FHIR_VERSION=${{parameters.version}} + ASSEMBLY_VER=$(assemblySemFileVer) + projectName: ${{parameters.version}} + additionalImageTags: ${{parameters.tag}} diff --git a/.github/actions/docker-build/action.yml b/.github/actions/docker-build/action.yml new file mode 100644 index 0000000000..1ce5a0ec13 --- /dev/null +++ b/.github/actions/docker-build/action.yml @@ -0,0 +1,23 @@ +name: Docker Build +description: 'Builds images for all supported FHIR versions' + +inputs: + fhirSchemaVersion: + description: 'The FHIR schema version to package' + required: true + assemblyVersion: + description: 'The assembly version to use' + required: true + composeLocation: + description: 'The location of the docker-compose file' + required: true + +runs: + using: 'composite' + steps: + - name: Build Docker Image + shell: bash + run: | + echo "Building and pushing Docker images for FHIR schema version ${{inputs.fhirSchemaVersion}}" + cd build/docker + docker-compose build --build-arg FHIR_VERSION=${{inputs.fhirSchemaVersion}} --build-arg ASSEMBLY_VER=${{inputs.assemblyVersion}} diff --git a/.github/actions/dotnet-build/action.yml b/.github/actions/dotnet-build/action.yml new file mode 100644 index 0000000000..01d875b7e3 --- /dev/null +++ b/.github/actions/dotnet-build/action.yml @@ -0,0 +1,29 @@ +name: dotnet build +description: Builds the packages and ensures their quality by running tests. +inputs: + assemblyVersion: + description: The scaler assembly's version. + required: true + buildConfiguration: + default: Debug + description: The dotnet build configuration. + required: false + fileVersion: + description: The scaler assembly's file version. + required: true + informationalVersion: + description: The scaler assembly's informational version. + required: true + majorMinorPatch: + description: The major.minor.patch version to use. + required: true + dotnet-version: + description: 'The version of the .NET SDK to use' + required: true + default: '8.0.202' # Default version if not specified +runs: + using: composite + steps: + - name: Build + shell: bash + run: dotnet build "./Microsoft.Health.Fhir.sln" --configuration ${{inputs.buildConfiguration}} "-p:ContinuousIntegrationBuild=true;AssemblyVersion=${{inputs.assemblyVersion}};FileVersion=${{inputs.fileVersion}};InformationalVersion=${{inputs.informationalVersion}};Version=${{inputs.majorMinorPatch}}" -warnaserror diff --git a/.github/actions/dotnet-test-core/action.yml b/.github/actions/dotnet-test-core/action.yml new file mode 100644 index 0000000000..bf3fef1f6f --- /dev/null +++ b/.github/actions/dotnet-test-core/action.yml @@ -0,0 +1,12 @@ +name: dotnet test +description: 'Runs the unit tests for the Fhir solution' +inputs: + buildConfiguration: + description: 'The build configuration to use' + required: true +runs: + using: 'composite' + steps: + - name: Run Unit Tests + shell: bash + run: dotnet test "Microsoft.Health.Fhir.sln" -p:ContinuousIntegrationBuild=true; --filter "FullyQualifiedName~Core.UnitTests" --configuration ${{inputs.buildConfiguration}} --no-build --verbosity normal diff --git a/.github/actions/dotnet-test-web/action.yml b/.github/actions/dotnet-test-web/action.yml new file mode 100644 index 0000000000..1d6f42ff06 --- /dev/null +++ b/.github/actions/dotnet-test-web/action.yml @@ -0,0 +1,12 @@ +name: dotnet test +description: 'Runs the unit tests for the Fhir solution' +inputs: + buildConfiguration: + description: 'The build configuration to use' + required: true +runs: + using: 'composite' + steps: + - name: Run Unit Tests + shell: bash + run: dotnet test "Microsoft.Health.Fhir.sln" -p:ContinuousIntegrationBuild=true; --filter "FullyQualifiedName~Web.UnitTests" --configuration ${{inputs.buildConfiguration}} --no-build --verbosity normal diff --git a/.github/actions/e2e-setup.yml b/.github/actions/e2e-setup.yml new file mode 100644 index 0000000000..33890f84d2 --- /dev/null +++ b/.github/actions/e2e-setup.yml @@ -0,0 +1,23 @@ +steps: + - task: DownloadBuildArtifacts@0 + inputs: + buildType: 'current' + downloadType: 'single' + downloadPath: '$(System.ArtifactsDirectory)' + artifactName: 'IntegrationTests' + + - task: UseDotNet@2 + inputs: + useGlobalJson: true + + - task: AzureKeyVault@1 + displayName: 'Azure Key Vault: resolute-oss-tenant-info' + inputs: + azureSubscription: $(ConnectedServiceName) + KeyVaultName: 'resolute-oss-tenant-info' + + - task: AzureKeyVault@1 + displayName: 'Azure Key Vault: $(DeploymentEnvironmentName)-ts' + inputs: + azureSubscription: $(ConnectedServiceName) + KeyVaultName: '$(DeploymentEnvironmentName)-ts' diff --git a/.github/actions/e2e-tests-extract.yml b/.github/actions/e2e-tests-extract.yml new file mode 100644 index 0000000000..1d6a11bf0e --- /dev/null +++ b/.github/actions/e2e-tests-extract.yml @@ -0,0 +1,10 @@ +parameters: +- name: version + type: string + +steps: + - task: ExtractFiles@1 + displayName: 'Extract E2E Test Binaries' + inputs: + archiveFilePatterns: '$(System.ArtifactsDirectory)/IntegrationTests/Microsoft.Health.Fhir.${{ parameters.version }}.Tests.E2E.zip' + destinationFolder: '$(Agent.TempDirectory)/E2ETests/' diff --git a/.github/actions/e2e-tests.yml b/.github/actions/e2e-tests.yml new file mode 100644 index 0000000000..b69ef2351d --- /dev/null +++ b/.github/actions/e2e-tests.yml @@ -0,0 +1,139 @@ +parameters: +- name: version + type: string +- name: appServiceName + type: string +- name: appServiceType + type: string + +steps: + - template: e2e-tests-extract.yml + parameters: + version: ${{parameters.version}} + + - task: AzurePowerShell@4 + displayName: 'Set Variables' + inputs: + azureSubscription: $(ConnectedServiceName) + azurePowerShellVersion: latestVersion + ScriptType: inlineScript + Inline: | + $keyVault = "$(DeploymentEnvironmentName)-ts" + $secrets = Get-AzKeyVaultSecret -VaultName $keyVault + + foreach($secret in $secrets) + { + $environmentVariableName = $secret.Name.Replace("--","_") + + $secretValue = Get-AzKeyVaultSecret -VaultName $keyVault -Name $secret.Name + # Replace with -AsPlainText flag when v5.3 of the Az Module is supported + $plainValue = ([System.Net.NetworkCredential]::new("", $secretValue.SecretValue).Password).ToString() + if([string]::IsNullOrEmpty($plainValue)) + { + throw "$($secret.Name) is empty" + } + Write-Host "##vso[task.setvariable variable=$($environmentVariableName)]$($plainValue)" + } + + $storageAccounts = Get-AzStorageAccount -ResourceGroupName $(ResourceGroupName) + $allStorageAccounts = "" + foreach ($storageAccount in $storageAccounts) { + $accKey = Get-AzStorageAccountKey -ResourceGroupName $(ResourceGroupName) -Name $storageAccount.StorageAccountName | Where-Object {$_.KeyName -eq "key1"} + + $storageSecretName = "$($storageAccount.StorageAccountName)_secret" + Write-Host "##vso[task.setvariable variable=$($storageSecretName)]$($accKey.Value)" + $allStorageAccounts += "$($storageSecretName)|$($accKey.Value)|" + } + Write-Host "##vso[task.setvariable variable=AllStorageAccounts]$($allStorageAccounts)" + + $appServiceName = "${{ parameters.appServiceName }}" + $appSettings = (Get-AzWebApp -ResourceGroupName $(ResourceGroupName) -Name $appServiceName).SiteConfig.AppSettings + $acrSettings = $appSettings | where {$_.Name -eq "FhirServer__Operations__ConvertData__ContainerRegistryServers__0"} + $acrLoginServer = $acrSettings[0].Value + $acrAccountName = ($acrLoginServer -split '\.')[0] + $acrPassword = (Get-AzContainerRegistryCredential -ResourceGroupName $(ResourceGroupName) -Name $acrAccountName).Password + Write-Host "##vso[task.setvariable variable=TestContainerRegistryServer]$($acrLoginServer)" + Write-Host "##vso[task.setvariable variable=TestContainerRegistryPassword]$($acrPassword)" + + $exportStoreSettings = $appSettings | where {$_.Name -eq "FhirServer__Operations__Export__StorageAccountUri"} + $exportStoreUri = $exportStoreSettings[0].Value + Write-Host "$exportStoreUri" + $exportStoreAccountName = [System.Uri]::new("$exportStoreUri").Host.Split('.')[0] + $exportStoreKey = Get-AzStorageAccountKey -ResourceGroupName $(ResourceGroupName) -Name "$exportStoreAccountName" | Where-Object {$_.KeyName -eq "key1"} + + Write-Host "##vso[task.setvariable variable=TestExportStoreUri]$($exportStoreUri)" + Write-Host "##vso[task.setvariable variable=TestExportStoreKey]$($exportStoreKey.Value)" + + $integrationStoreSettings = $appSettings | where {$_.Name -eq "FhirServer__Operations__IntegrationDataStore__StorageAccountUri"} + $integrationStoreUri = $integrationStoreSettings[0].Value + Write-Host "$integrationStoreUri" + $integrationStoreAccountName = [System.Uri]::new("$integrationStoreUri").Host.Split('.')[0] + $integrationStoreKey = Get-AzStorageAccountKey -ResourceGroupName $(ResourceGroupName) -Name "$integrationStoreAccountName" | Where-Object {$_.KeyName -eq "key1"} + + Write-Host "##vso[task.setvariable variable=TestIntegrationStoreUri]$($integrationStoreUri)" + Write-Host "##vso[task.setvariable variable=TestIntegrationStoreKey]$($integrationStoreKey.Value)" + + Write-Host "##vso[task.setvariable variable=Resource]$(TestApplicationResource)" + + $secrets = Get-AzKeyVaultSecret -VaultName resolute-oss-tenant-info + + foreach($secret in $secrets) + { + $environmentVariableName = $secret.Name.Replace("--","_") + + $secretValue = Get-AzKeyVaultSecret -VaultName resolute-oss-tenant-info -Name $secret.Name + # Replace with -AsPlainText flag when v5.3 of the Az Module is supported + $plainValue = ([System.Net.NetworkCredential]::new("", $secretValue.SecretValue).Password).ToString() + if([string]::IsNullOrEmpty($plainValue)) + { + throw "$($secret.Name) is empty" + } + Write-Host "##vso[task.setvariable variable=$($environmentVariableName)]$($plainValue)" + } + # ---------------------------------------- + + dotnet dev-certs https + + - task: DotNetCoreCLI@2 + displayName: 'E2E ${{ parameters.version }} ${{parameters.appServiceType}}' + inputs: + command: test + arguments: '"$(Agent.TempDirectory)/E2ETests/**/*${{ parameters.version }}.Tests.E2E*.dll" --blame-hang-timeout 7m --filter "FullyQualifiedName~${{parameters.appServiceType}}&Category!=ExportLongRunning"' + workingDirectory: "$(System.ArtifactsDirectory)" + testRunTitle: '${{ parameters.version }} ${{parameters.appServiceType}}' + env: + 'TestEnvironmentUrl': $(TestEnvironmentUrl) + 'TestEnvironmentUrl_${{ parameters.version }}': $(TestEnvironmentUrl_${{ parameters.version }}) + 'TestEnvironmentUrl_Sql': $(TestEnvironmentUrl_Sql) + 'TestEnvironmentUrl_${{ parameters.version }}_Sql': $(TestEnvironmentUrl_${{ parameters.version }}_Sql) + 'Resource': $(Resource) + 'AllStorageAccounts': $(AllStorageAccounts) + 'TestContainerRegistryServer': $(TestContainerRegistryServer) + 'TestContainerRegistryPassword': $(TestContainerRegistryPassword) + 'TestExportStoreUri': $(TestExportStoreUri) + 'TestExportStoreKey': $(TestExportStoreKey) + 'TestIntegrationStoreUri': $(TestIntegrationStoreUri) + 'TestIntegrationStoreKey': $(TestIntegrationStoreKey) + 'tenant-admin-service-principal-name': $(tenant-admin-service-principal-name) + 'tenant-admin-service-principal-password': $(tenant-admin-service-principal-password) + 'tenant-admin-user-name': $(tenant-admin-user-name) + 'tenant-admin-user-password': $(tenant-admin-user-password) + 'tenant-id': $(tenant-id) + 'app_globalAdminServicePrincipal_id': $(app_globalAdminServicePrincipal_id) + 'app_globalAdminServicePrincipal_secret': $(app_globalAdminServicePrincipal_secret) + 'app_nativeClient_id': $(app_nativeClient_id) + 'app_nativeClient_secret': $(app_nativeClient_secret) + 'app_wrongAudienceClient_id': $(app_wrongAudienceClient_id) + 'app_wrongAudienceClient_secret': $(app_wrongAudienceClient_secret) + 'user_globalAdminUser_id': $(user_globalAdminUser_id) + 'user_globalAdminUser_secret': $(user_globalAdminUser_secret) + 'user_globalConverterUser_id': $(user_globalConverterUser_id) + 'user_globalConverterUser_secret': $(user_globalConverterUser_secret) + 'user_globalExporterUser_id': $(user_globalExporterUser_id) + 'user_globalExporterUser_secret': $(user_globalExporterUser_secret) + 'user_globalImporterUser_id': $(user_globalImporterUser_id) + 'user_globalImporterUser_secret': $(user_globalImporterUser_secret) + 'user_globalReaderUser_id': $(user_globalReaderUser_id) + 'user_globalReaderUser_secret': $(user_globalReaderUser_secret) + 'user_globalWriterUser_id': $(user_globalWriterUser_id) + 'user_globalWriterUser_secret': $(user_globalWriterUser_secret) diff --git a/.github/actions/package-integration-tests.yml b/.github/actions/package-integration-tests.yml new file mode 100644 index 0000000000..180c259c07 --- /dev/null +++ b/.github/actions/package-integration-tests.yml @@ -0,0 +1,17 @@ +steps: + + - task: DotNetCoreCLI@2 + displayName: 'dotnet publish Integration Tests' + inputs: + command: publish + projects: 'test/**/*.csproj' + arguments: '--version-suffix $(build.buildNumber) -o "$(build.binariesdirectory)/IntegrationTests" --configuration $(buildConfiguration) --no-build -f $(defaultBuildFramework)' + publishWebProjects: false + zipAfterPublish: true + + - task: PublishBuildArtifacts@1 + displayName: 'publish Integration Tests' + inputs: + pathToPublish: '$(build.binariesdirectory)/IntegrationTests' + artifactName: 'IntegrationTests' + artifactType: 'container' \ No newline at end of file diff --git a/.github/actions/package-web-build-artifacts/action.yml b/.github/actions/package-web-build-artifacts/action.yml new file mode 100644 index 0000000000..682417286f --- /dev/null +++ b/.github/actions/package-web-build-artifacts/action.yml @@ -0,0 +1,27 @@ +name: Package Web Build Artifacts +description: 'Packages the web build artifacts for deployment' +inputs: + fhirSchemaVersion: + description: 'The FHIR schema version to package' + required: true + majorMinorPatch: + description: 'The version of the Nuget package' + required: true + outputPath: + description: 'The path to the output directory' + required: true + buildConfiguration: + description: 'The build configuration to use' + required: true + semVer: + description: 'The SemVer to use' + required: true +runs: + using: 'composite' + steps: + - name: Publish Web Artifacts + shell: bash + run: | + echo "Publishing web artifacts for FHIR schema version ${{inputs.fhirSchemaVersion}}" + dotnet publish ${{github.workspace}}/src/Microsoft.Health.Fhir.${{inputs.fhirSchemaVersion}}.Web/Microsoft.Health.Fhir.${{inputs.fhirSchemaVersion}}.Web.csproj --output ${{inputs.outputPath}}/deploy/Microsoft.Health.Fhir.${{inputs.fhirSchemaVersion}}.Web --configuration ${{inputs.buildConfiguration}} --version-suffix ${{inputs.semVer}} --no-build -f ${{env.defaultDotNetVersion}} + zip Microsoft.Health.Fhir.${{inputs.fhirSchemaVersion}}.Web.zip ${{inputs.outputPath}}/deploy/Microsoft.Health.Fhir.${{inputs.fhirSchemaVersion}}.Web diff --git a/.github/actions/package-web.yml b/.github/actions/package-web.yml new file mode 100644 index 0000000000..edc1378081 --- /dev/null +++ b/.github/actions/package-web.yml @@ -0,0 +1,14 @@ +parameters: + csproj: '**/*Web.csproj' + +steps: + + # Package web + + - task: DotNetCoreCLI@2 + displayName: 'dotnet publish ${{parameters.csproj}}' + inputs: + command: publish + projects: '${{parameters.csproj}}' + arguments: '--output $(build.artifactStagingDirectory)/web --configuration $(buildConfiguration) --version-suffix $(build.buildNumber) --no-build -f $(defaultBuildFramework)' + publishWebProjects: false \ No newline at end of file diff --git a/.github/actions/package.yml b/.github/actions/package.yml new file mode 100644 index 0000000000..0fbf89aa9e --- /dev/null +++ b/.github/actions/package.yml @@ -0,0 +1,93 @@ +steps: + + # Package web + - template: package-web.yml + parameters: + csproj: '**/Microsoft.Health.Fhir.Stu3.Web.csproj' + + - template: package-web.yml + parameters: + csproj: '**/Microsoft.Health.Fhir.R4.Web.csproj' + + - template: package-web.yml + parameters: + csproj: '**/Microsoft.Health.Fhir.R4B.Web.csproj' + + - template: package-web.yml + parameters: + csproj: '**/Microsoft.Health.Fhir.R5.Web.csproj' + + # Package nugets + - powershell: | + & dotnet pack $(Build.SourcesDirectory) --output $(Build.ArtifactStagingDirectory)/nupkgs --no-build --configuration=Release -p:PackageVersion=$(nuGetVersion) + name: PackNugets + + # Publish artifacts + - task: PublishBuildArtifacts@1 + displayName: 'publish web artifacts' + inputs: + pathToPublish: '$(build.artifactStagingDirectory)/web' + artifactName: 'deploy' + artifactType: 'container' + + - task: PublishBuildArtifacts@1 + displayName: 'publish samples' + inputs: + pathToPublish: './samples/' + artifactName: 'deploy' + artifactType: 'container' + + - task: PublishBuildArtifacts@1 + displayName: 'publish testauthenvironment.json' + inputs: + pathToPublish: './testauthenvironment.json' + artifactName: 'deploy' + artifactType: 'container' + + - task: PublishBuildArtifacts@1 + displayName: 'publish global.json' + inputs: + pathToPublish: './global.json' + artifactName: 'deploy' + artifactType: 'container' + + - task: PublishBuildArtifacts@1 + displayName: 'publish test configuration jsons' + inputs: + pathToPublish: './test/Configuration/' + artifactName: 'deploy' + artifactType: 'container' + + - task: PublishBuildArtifacts@1 + displayName: 'publish release directory' + inputs: + pathToPublish: './release/' + artifactName: 'deploy' + artifactType: 'container' + + - task: PublishBuildArtifacts@1 + displayName: 'publish nuget artifacts' + inputs: + pathtoPublish: '$(build.artifactStagingDirectory)/nupkgs' + artifactName: 'nuget' + publishLocation: 'container' + + - task: CopyFiles@2 + displayName: 'copy symbols' + inputs: + sourceFolder: '$(build.sourcesDirectory)' + contents: | + **/*.pdb + !**/*.UnitTests.pdb + targetFolder: '$(build.artifactStagingDirectory)/symbols' + cleanTargetFolder: true + flattenFolders: true + overWrite: true + + - task: PublishBuildArtifacts@1 + displayName: 'publish symbol artifacts' + inputs: + pathtoPublish: '$(build.artifactStagingDirectory)/symbols' + artifactName: 'symbols' + publishLocation: 'container' + \ No newline at end of file diff --git a/.github/actions/provision-healthcheck.yml b/.github/actions/provision-healthcheck.yml new file mode 100644 index 0000000000..40107e6ebf --- /dev/null +++ b/.github/actions/provision-healthcheck.yml @@ -0,0 +1,26 @@ +parameters: +- name: webAppName + type: string + +steps: +- powershell: | + $webAppName = "${{ parameters.webAppName }}".ToLower() + $healthCheckUrl = "https://$webAppName.azurewebsites.net/health/check" + $healthStatus = 0 + Do { + Start-Sleep -s 5 + Write-Host "Checking: $healthCheckUrl" + + try { + $healthStatus = (Invoke-WebRequest -URI $healthCheckUrl).statuscode + Write-Host "Result: $healthStatus" + } + catch { + Write-Host $PSItem.Exception.Message + } + finally { + $Error.Clear() + } + + } While ($healthStatus -ne 200) + name: PingHealthCheckEndpoint diff --git a/.github/actions/provision-sqlServer.yml b/.github/actions/provision-sqlServer.yml new file mode 100644 index 0000000000..190add675e --- /dev/null +++ b/.github/actions/provision-sqlServer.yml @@ -0,0 +1,44 @@ + +parameters: +- name: resourceGroup + type: string +- name: sqlServerName + type: string +- name: schemaAutomaticUpdatesEnabled + type: string + default: 'auto' +- name: sqlServerAdminPassword + type: string + default: '' + +jobs: +- job: provisionEnvironment + pool: + name: '$(SharedLinuxPool)' + vmImage: '$(LinuxVmImage)' + steps: + - task: AzureKeyVault@1 + displayName: 'Azure Key Vault: resolute-oss-tenant-info' + inputs: + azureSubscription: $(ConnectedServiceName) + KeyVaultName: 'resolute-oss-tenant-info' + + - task: AzurePowerShell@5 + displayName: 'Azure PowerShell script: InlineScript' + inputs: + azureSubscription: $(ConnectedServiceName) + azurePowerShellVersion: latestVersion + ScriptType: inlineScript + Inline: | + Add-Type -AssemblyName System.Web + + $templateParameters = @{ + sqlAdminPassword = "${{parameters.sqlServerAdminPassword}}" + sqlServerName = "${{parameters.sqlServerName}}".ToLower() + sqlSchemaAutomaticUpdatesEnabled = "${{parameters.schemaAutomaticUpdatesEnabled}}" + } + + Write-Host "Provisioning Sql server" + Write-Host "Resource Group: ${{ parameters.resourceGroup }}" + Write-Host "SqlServerName: ${{ parameters.sqlServerName }}" + New-AzResourceGroupDeployment -ResourceGroupName "${{ parameters.resourceGroup }}" -TemplateFile $(System.DefaultWorkingDirectory)/samples/templates/default-sqlServer.json -TemplateParameterObject $templateParameters -Verbose diff --git a/.github/actions/redeploy-webapp.yml b/.github/actions/redeploy-webapp.yml new file mode 100644 index 0000000000..6845260147 --- /dev/null +++ b/.github/actions/redeploy-webapp.yml @@ -0,0 +1,29 @@ +parameters: +- name: version + type: string +- name: webAppName + type: string +- name: subscription + type: string +- name: imageTag + type: string + +jobs: +- job: provisionEnvironment + pool: + name: '$(DefaultLinuxPool)' + vmImage: '$(LinuxVmImage)' + steps: + - task: AzureRmWebAppDeployment@4 + displayName: 'Azure App Service Deploy' + inputs: + azureSubscription: '${{ parameters.subscription }}' + appType: 'webAppContainer' + WebAppName: '${{ parameters.webAppName }}' + DockerNamespace: $(azureContainerRegistry) + DockerRepository: '${{ parameters.version }}_fhir-server' + DockerImageTag: ${{ parameters.imageTag }} + + - template: ./provision-healthcheck.yml + parameters: + webAppName: ${{ parameters.webAppName }} \ No newline at end of file diff --git a/.github/actions/run-tests.yml b/.github/actions/run-tests.yml new file mode 100644 index 0000000000..d8672f3c1c --- /dev/null +++ b/.github/actions/run-tests.yml @@ -0,0 +1,79 @@ +parameters: +- name: version + type: string +- name: keyVaultName + type: string +- name: appServiceName + type: string +jobs: +- job: "integrationTests" + pool: + name: '$(SharedLinuxPool)' + vmImage: '$(LinuxVmImage)' + steps: + - task: DownloadBuildArtifacts@0 + inputs: + buildType: 'current' + downloadType: 'single' + downloadPath: '$(System.ArtifactsDirectory)' + artifactName: 'IntegrationTests' + + - task: ExtractFiles@1 + displayName: 'Extract Integration Test Binaries' + inputs: + archiveFilePatterns: '$(System.ArtifactsDirectory)/IntegrationTests/Microsoft.Health.Fhir.${{ parameters.version }}.Tests.Integration.zip' + destinationFolder: '$(Agent.TempDirectory)/IntegrationTests/' + + - task: UseDotNet@2 + inputs: + useGlobalJson: true + + - task: AzureKeyVault@1 + displayName: 'Azure Key Vault: ${{ parameters.keyVaultName }}' + inputs: + azureSubscription: $(ConnectedServiceName) + KeyVaultName: '${{ parameters.keyVaultName }}' + + - task: AzureKeyVault@1 + displayName: 'Azure Key Vault: ${{ parameters.keyVaultName }}-sql' + inputs: + azureSubscription: $(ConnectedServiceName) + KeyVaultName: '${{ parameters.keyVaultName }}-sql' + + - task: DotNetCoreCLI@2 + displayName: 'Run Integration Tests' + inputs: + command: test + arguments: '"$(Agent.TempDirectory)/IntegrationTests/**/*${{ parameters.version }}.Tests.Integration*.dll" --blame-hang-timeout 15m' + workingDirectory: "$(System.ArtifactsDirectory)" + testRunTitle: '${{ parameters.version }} Integration' + env: + 'CosmosDb:Host': $(CosmosDb--Host) + 'CosmosDb:Key': $(CosmosDb--Key) + 'SqlServer:ConnectionString': $(SqlServer--ConnectionString) + +- job: 'cosmosE2eTests' + dependsOn: [] + pool: + name: '$(SharedLinuxPool)' + vmImage: '$(LinuxVmImage)' + steps: + - template: e2e-setup.yml + - template: e2e-tests.yml + parameters: + version: ${{ parameters.version }} + appServiceName: ${{ parameters.appServiceName }} + appServiceType: 'CosmosDb' + +- job: 'sqlE2eTests' + dependsOn: [] + pool: + name: '$(SharedLinuxPool)' + vmImage: '$(LinuxVmImage)' + steps: + - template: e2e-setup.yml + - template: e2e-tests.yml + parameters: + version: ${{ parameters.version }} + appServiceName: '${{ parameters.appServiceName }}-sql' + appServiceType: 'SqlServer' diff --git a/.github/actions/setup-build-variables/action.yml b/.github/actions/setup-build-variables/action.yml new file mode 100644 index 0000000000..b78873f84b --- /dev/null +++ b/.github/actions/setup-build-variables/action.yml @@ -0,0 +1,38 @@ +name: setup build variables +description: Sets variables used during builds. + +runs: + using: composite + steps: + - name: Set Build Variables + id: defaultVariables + shell: bash + run: | + echo "buildConfiguration=Release" >> "$GITHUB_ENV" + echo "defaultBuildFramework=net8.0" >> "$GITHUB_ENV" + echo "azureSubscriptionEndpoint=docker-build" >> "$GITHUB_ENV" + echo "azureContainerRegistryName=healthplatformregistry" >> "$GITHUB_ENV" + echo "connectedServiceName=Microsoft Health Open Source Subscription" >> "$GITHUB_ENV" + echo "composeLocation=build/docker/docker-compose.yaml" >> "$GITHUB_ENV" + + - name: Set Build Urls using Deployment Environment + shell: bash + run: | + echo "azureContainerRegistry='$azureContainerRegistryName'.azurecr.io" >> "$GITHUB_ENV" + echo "deploymentEnvironmentNameSql='$deploymentEnvironmentName-sql' >> "$GITHUB_ENV" + echo "deploymentEnvironmentNameR4='$deploymentEnvironmentName-r4' >> "$GITHUB_ENV" + echo "deploymentEnvironmentNameR4Sql='$deploymentEnvironmentNameR4'-sql >> "$GITHUB_ENV" + echo "deploymentEnvironmentNameR4B='$deploymentEnvironmentName-r4b' >> "$GITHUB_ENV" + echo "deploymentEnvironmentNameR4BSql='$deploymentEnvironmentNameR4B'-sql >> "$GITHUB_ENV" + echo "deploymentEnvironmentNameR5='$deploymentEnvironmentName'-r5 >> "$GITHUB_ENV" + echo "deploymentEnvironmentNameR5Sql='$deploymentEnvironmentNameR5'-sql >> "$GITHUB_ENV" + echo "testEnvironmentUrl=https://'$deploymentEnvironmentName'.azurewebsites.net >> "$GITHUB_ENV" + echo "testEnvironmentUrl_Sql=https://'$deploymentEnvironmentName'-sql.azurewebsites.net >> "$GITHUB_ENV" + echo "testEnvironmentUrl_R4=https://'$deploymentEnvironmentName'-r4.azurewebsites.net >> "$GITHUB_ENV" + echo "testEnvironmentUrl_R4_Sql=https://'$deploymentEnvironmentName'-r4-sql.azurewebsites.net >> "$GITHUB_ENV" + echo "testEnvironmentUrl_R4B=https://'$deploymentEnvironmentName'-r4b.azurewebsites.net >> "$GITHUB_ENV" + echo "testEnvironmentUrl_R4B_Sql=https://'$deploymentEnvironmentName'-r4b-sql.azurewebsites.net >> "$GITHUB_ENV" + echo "testEnvironmentUrl_R5=https://'$deploymentEnvironmentName'-r5.azurewebsites.net >> "$GITHUB_ENV" + echo "testEnvironmentUrl_R5_Sql=https://'$deploymentEnvironmentName'-r5-sql.azurewebsites.net >> "$GITHUB_ENV" + echo "testClientUrl=https://'$deploymentEnvironmentName'-client/ >> "$GITHUB_ENV" + echo "testApplicationResource=https://'$deploymentEnvironmentName'.'$tenantDomain' >> "$GITHUB_ENV" diff --git a/.github/actions/update-semver/action.yml b/.github/actions/update-semver/action.yml new file mode 100644 index 0000000000..ccaa23c812 --- /dev/null +++ b/.github/actions/update-semver/action.yml @@ -0,0 +1,39 @@ +name: update-semver +description: 'Update the build number with the SemVer from GitVersion' +inputs: + configFilePath: + description: 'Path to the GitVersion configuration file' + required: false + default: './GitVersion.yml' +outputs: + assemblyVersion: + description: The assembly version for the build + value: ${{ steps.version.outputs.GitVersion_AssemblySemVer }} + fileVersion: + description: The assembly file version for the build + value: ${{ steps.version.outputs.GitVersion_AssemblySemFileVer }} + informationalVersion: + description: The assembly informational version for the build + value: ${{ steps.version.outputs.GitVersion_InformationalVersion }} + semVer: + description: The NuGet package version for the build + value: ${{ steps.version.outputs.GitVersion_SemVer }} + majorMinorPatch: + description: The major.minor.patch version for the build + value: ${{ steps.version.outputs.GitVersion_MajorMinorPatch }} +runs: + using: 'composite' + steps: + + - name: Install GitVersion' + uses: gittools/actions/gitversion/setup@v0.13.4 + with: + versionSpec: '5.x' + + - name: SetVariablesFromGitVersion + id: version + uses: gittools/actions/gitversion/execute@v0.13.4 + with: + configFilePath: ${{inputs.configFilePath}} + targetPath: ${{github.workspace}} + useConfigFile: true diff --git a/.github/actions/update-sqlAdminPassword.yml b/.github/actions/update-sqlAdminPassword.yml new file mode 100644 index 0000000000..b1d4a2466e --- /dev/null +++ b/.github/actions/update-sqlAdminPassword.yml @@ -0,0 +1,18 @@ +steps: + +- task: UseDotNet@2 + displayName: 'Use .NET Core sdk (to generate password)' + inputs: + packageType: sdk + version: 3.1.x + +- task: UseDotNet@2 + inputs: + useGlobalJson: true + +- powershell: | + + $random = -join((((33,35,37,38,42,43,45,46,95) + (48..57) + (65..90) + (97..122) | Get-Random -Count 20) + ((33,35,37,38,42,43,45,46,95) | Get-Random -Count 1) + ((48..57) | Get-Random -Count 1) + ((65..90) | Get-Random -Count 1) + ((97..122) | Get-Random -Count 1) | Get-Random -Count 24) | % {[char]$_}) + Write-Host "##vso[task.setvariable variable=password;isOutput=true]" + + name: SetVariablesFromRandomString diff --git a/.github/workflows/fhir-oss-ci-pipeline.yml b/.github/workflows/fhir-oss-ci-pipeline.yml new file mode 100644 index 0000000000..30e3542060 --- /dev/null +++ b/.github/workflows/fhir-oss-ci-pipeline.yml @@ -0,0 +1,261 @@ +# DESCRIPTION: +# Builds, tests, and packages the solution for the main branch. + +on: + pull_request + +permissions: + id-token: write + contents: read + +defaults: + run: + working-directory: src + shell: bash + +env: + buildConfiguration: Release + azureSubscriptionEndpoint: docker-build + azureContainerRegistryName: healthplatformregistry + connectedServiceName: Microsoft Health Open Source Subscription + composeLocation: build/docker/docker-compose.yaml + imageTag: ${{github.run_number}} + outputPath: ${{github.workspace}}/artifacts + defaultDotNetVersion: net8.0 + +jobs: + setup: + runs-on: [self-hosted, 1ES.Pool=GithubRunPool] + env: + deploymentEnvironmentName: $vars.CIRESOURCEGROUPROOT + appServicePlanName: $vars.CIRESOURCEGROUPROOT-linux + resourceGroupName: $vars.CIRESOURCEGROUPROOT + outputs: + assemblyVersion: ${{ steps.version.outputs.assemblyVersion }} + fileVersion: ${{ steps.version.outputs.fileVersion }} + informationalVersion: ${{ steps.version.outputs.informationalVersion }} + majorMinorPatch: ${{ steps.version.outputs.majorMinorPatch }} + semVer: ${{steps.version.outputs.SemVer}} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Latest .Net SDK + uses: actions/setup-dotnet@v4 + with: + global-json-file: 'global.json' + dotnet-version: | + 8.x + + - name: Determine Semver + id: version + uses: ./.github/actions/update-semver + + # - name: Azure Login + # uses: azure/login@v2 + # with: + # client-id: ${{secrets.AZURE_CLIENT_ID}} + # subscription-id: ${{secrets.AZURE_SUBSCRIPTION_ID}} + # tenant-id: ${{secrets.AZURE_TENANT_ID}} + # enable-AzPSSession: true + + # - name: Clean Storage Accounts + # uses: ./.github/actions/clean-storage-accounts + # with: + # environmentName: ${{vars.CIRESOURCEGROUPROOT}} + # - name: Cleanup Integration Test databases + # uses: ./.github/actions/cleanup-integration-test-databases + # with: + # environmentName: ${{vars.CIRESOURCEGROUPROOT}} + buildAndUnitTest: + runs-on: windows-latest + needs: setup + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check DotNet Version + run: dotnet --version + + - name: Build + uses: ./.github/actions/dotnet-build + with: + assemblyVersion: ${{needs.setup.outputs.assemblyVersion}} + buildConfiguration: ${{env.buildConfiguration}} + fileVersion: ${{needs.setup.outputs.fileVersion}} + informationalVersion: ${{needs.setup.outputs.informationalVersion}} + majorMinorPatch: ${{needs.setup.outputs.majorMinorPatch}} + - name: TestCore + uses: ./.github/actions/dotnet-test-core + with: + buildConfiguration: ${{env.buildConfiguration}} + - name: TestWeb + uses: ./.github/actions/dotnet-test-web + with: + buildConfiguration: ${{env.buildConfiguration}} + + # - name: Generate SBOM + # run: | + # curl -Lo $RUNNER_TEMP/sbom-tool https://github.com/microsoft/sbom-tool/releases/latest/download/sbom-tool-linux-x64 + # chmod +x $RUNNER_TEMP/sbom-tool + # $RUNNER_TEMP/sbom-tool generate -b ${{env.outputPath}} -bc . -V Verbose -ps "Organization: Microsoft" -pv ${{needs.setup.outputs.majorMinorPatch}} -pn ${{needs.setup.outputs.informationalVersion}} + + # - name: Upload a Build Artifact + # uses: actions/upload-artifact@v4 + # with: + # name: build + # path: ${{env.outputPath}} + - name: Create Nuget packages + shell: bash + run: | + echo "Creating Nuget packages" + dotnet pack ${{github.workspace}}\Microsoft.Health.Fhir.sln --output ${{env.outputPath}}/nupkgs --no-build --configuration=${{env.buildConfiguration}} -p:PackageVersion=${{needs.setup.outputs.majorMinorPatch}} + + - name: Upload Nuget Packages + uses: actions/upload-artifact@v4 + with: + name: nuget + path: ${{env.outputPath}}/nupkgs + + - name: samples + shell: bash + run: | + echo "Copying samples to deploy directory" + cp -r ${{github.workspace}}/samples ${{env.outputPath}}/deploy + + - name: Copying testauthenvironment.json to deploy directory + shell: bash + run: | + echo "Copying testauthenvironment.json to deploy directory" + cp ${{github.workspace}}/testauthenvironment.json ${{env.outputPath}}/deploy/ + + - name: Copying global.json to deploy directory + shell: bash + run: | + echo "Copying global.json to deploy directory" + cp ${{github.workspace}}/global.json ${{env.outputPath}}/deploy/ + + - name: Copying test configuration json to deploy directory + shell: bash + run: | + echo "Copying test configuration json to deploy directory" + cp ${{github.workspace}}/test/Configuration/testconfiguration.json ${{env.outputPath}}/deploy/ + + - name: Copying docker compose root file to deploy directory + shell: bash + run: | + echo "Copying docker compose root file to deploy directory" + cp ${{github.workspace}}/release/docker-compose.yaml ${{env.outputPath}}/deploy + + - name: Copying pdb files to symbols directory + shell: bash + run: | + echo "Copying pdb files to deploy symbols" + find ${{github.workspace}}/src -type f -name "*.pdb" ! -name "*UnitTest*" -exec cp {} ${{env.outputPath}}/symbols \; + + - name: Publish Stu3 Web Artifacts to deploy directory + uses: ./.github/actions/package-web-build-artifacts + with: + fhirschemaversion: "Stu3" + majorMinorPatch: ${{needs.setup.outputs.majorMinorPatch}} + outputPath: ${{env.outputPath}} + buildConfiguration: ${{env.buildConfiguration}} + semVer: ${{needs.setup.outputs.semVer}} + + - name: Publish R4 Web Artifacts to deploy directory + uses: ./.github/actions/package-web-build-artifacts + with: + fhirschemaversion: "R4" + majorMinorPatch: ${{needs.setup.outputs.majorMinorPatch}} + outputPath: ${{env.outputPath}} + buildConfiguration: ${{env.buildConfiguration}} + semVer: ${{needs.setup.outputs.semVer}} + + - name: Publish R4B Web Artifacts to deploy directory + uses: ./.github/actions/package-web-build-artifacts + with: + fhirschemaversion: "R4B" + majorMinorPatch: ${{needs.setup.outputs.majorMinorPatch}} + outputPath: ${{env.outputPath}} + buildConfiguration: ${{env.buildConfiguration}} + semVer: ${{needs.setup.outputs.semVer}} + + - name: Publish R5 Web Artifacts to deploy directory + uses: ./.github/actions/package-web-build-artifacts + with: + fhirschemaversion: "R5" + majorMinorPatch: ${{needs.setup.outputs.majorMinorPatch}} + outputPath: ${{env.outputPath}} + buildConfiguration: ${{env.buildConfiguration}} + semVer: ${{needs.setup.outputs.semVer}} + + - name: Docker Build Stu3 Image + uses: ./.github/actions/docker-build + with: + assemblyVersion: ${{needs.setup.outputs.majorMinorPatch}} + fhirSchemaVersion: "Stu3" + composeLocation: ${{env.composeLocation}} + + - name: Docker Build R4 Image + uses: ./.github/actions/docker-build + with: + assemblyVersion: ${{needs.setup.outputs.majorMinorPatch}} + fhirSchemaVersion: "R4" + composeLocation: ${{env.composeLocation}} + + - name: Docker Build R4B Image + uses: ./.github/actions/docker-build + with: + assemblyVersion: ${{needs.setup.outputs.majorMinorPatch}} + fhirSchemaVersion: "R4B" + composeLocation: ${{env.composeLocation}} + + - name: Docker Build R5 Image + uses: ./.github/actions/docker-build + with: + assemblyVersion: ${{needs.setup.outputs.majorMinorPatch}} + fhirSchemaVersion: "R5" + composeLocation: ${{env.composeLocation}} + + - name: Upload deploy directory + uses: actions/upload-artifact@v4 + with: + name: deploy + path: ${{env.outputPath}}/deploy + + # - name: Upload Symbols + # uses: actions/upload-artifact@v4 + # with: + # name: symbols + # path: ${{env.outputPath}}/bin/${{env.buildConfiguration}}/net5.0/publish + + # runIntegrationTests: + # runs-on: [self-hosted, 1ES.Pool=GithubRunPool, Windows] + # needs : buildAndUnitTest + # steps: + # - name: Checkout + # uses: actions/checkout@v4 + # with: + # fetch-depth: 0 + # - name: Download Build Artifact for Testing + # uses: actions/download-artifact@v4 + # with: + # path: artifacts + # - name: Install Latest .Net SDK + # uses: actions/setup-dotnet@v4 + # with: + # global-json-file: 'global.json' + # dotnet-version: | + # 6.x + # 8.x + # - name: Docker add main tag + # uses: ./.github/actions/docker-add-main-tag + # with: + # assemblySemFileVer: ${{needs.setup.outputs.semVer}} + # imageTag: ${{env.imageTag}} + # azureContainerRegistryName: ${{env.azureContainerRegistryName}} + # connectedServiceName: ${{env.connectedServiceName}} diff --git a/.github/workflows/simple-build.yml b/.github/workflows/simple-build.yml new file mode 100644 index 0000000000..8be0c7b84c --- /dev/null +++ b/.github/workflows/simple-build.yml @@ -0,0 +1,62 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +on: + pull_request + +env: + buildConfiguration: Release + azureSubscriptionEndpoint: docker-build + azureContainerRegistryName: healthplatformregistry + connectedServiceName: Microsoft Health Open Source Subscription + composeLocation: build/docker/docker-compose.yaml + imageTag: ${{github.run_number}} + outputPath: ${{github.workspace}}/artifacts + +jobs: + buildTestAndPackage: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.204 + + - name: Restore dependencies + run: dotnet restore + + - name: Build + shell: bash + run: dotnet build "./Microsoft.Health.Fhir.sln" --no-restore + # run: dotnet build "./Microsoft.Health.Fhir.sln" --configuration ${{inputs.buildConfiguration}} "-p:ContinuousIntegrationBuild=true;AssemblyVersion=${{inputs.assemblyVersion}};FileVersion=${{inputs.fileVersion}};InformationalVersion=${{inputs.informationalVersion}};Version=${{inputs.majorMinorPatch}}" -warnaserror + + # - name: Test + # run: dotnet test "Microsoft.Health.Fhir.sln" --no-build --filter "FullyQualifiedName~UnitTests" --verbosity normal + # # run: dotnet test "Microsoft.Health.Fhir.sln" -p:ContinuousIntegrationBuild=true; --filter "FullyQualifiedName~Core.UnitTests" --configuration ${{inputs.buildConfiguration}} --no-build --verbosity normal + + - name: Create Nuget packages + run: | + echo "Creating Nuget packages" + dotnet pack ${{github.workspace}}\Microsoft.Health.Fhir.sln --output ${{env.outputPath}}/nupkgs --no-build -c Release + # dotnet pack ${{github.workspace}}\Microsoft.Health.Fhir.sln --output ${{env.outputPath}}/nupkgs --no-build --configuration=${{env.buildConfiguration}} -p:PackageVersion=${{needs.setup.outputs.majorMinorPatch}} + + - name: Upload Nuget Packages + uses: actions/upload-artifact@v4 + with: + name: nuget + path: ${{env.outputPath}}/nupkgs + + - name: Login to Azure + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + #what is the compliant way to use secrets in github actions, look at + # https://docs.opensource.microsoft.com/ + + - name: Build Docker image + run: | + docker build -t myregistry.azurecr.io/myimage:${{ github.sha }} . + docker push myregistry.azurecr.io/myimage:${{ github.sha }} diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Operations/Import/ImportOrchestratorJobTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Operations/Import/ImportOrchestratorJobTests.cs index 7a964492f9..9c18ff502d 100644 --- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Operations/Import/ImportOrchestratorJobTests.cs +++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Operations/Import/ImportOrchestratorJobTests.cs @@ -32,6 +32,7 @@ namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Operations.Import { + [Collection("Sequential")] [Trait(Traits.OwningTeam, OwningTeam.FhirImport)] [Trait(Traits.Category, Categories.Import)] public class ImportOrchestratorJobTests diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Operations/Import/ImportProcessingJobTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Operations/Import/ImportProcessingJobTests.cs index a325118c65..3070e3821a 100644 --- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Operations/Import/ImportProcessingJobTests.cs +++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Operations/Import/ImportProcessingJobTests.cs @@ -29,6 +29,7 @@ namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Operations.Import { + [Collection("Sequential")] [Trait(Traits.OwningTeam, OwningTeam.FhirImport)] [Trait(Traits.Category, Categories.Import)] public class ImportProcessingJobTests diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/CustomQueriesUnitTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/CustomQueriesUnitTests.cs index 736493dd7b..09c328ae80 100644 --- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/CustomQueriesUnitTests.cs +++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/CustomQueriesUnitTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search { + [Collection("Sequential")] [Trait(Traits.OwningTeam, OwningTeam.Fhir)] [Trait(Traits.Category, Categories.Search)] public class CustomQueriesUnitTests diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/FlatteningRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/FlatteningRewriterTests.cs index da35abbc56..568095f336 100644 --- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/FlatteningRewriterTests.cs +++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/FlatteningRewriterTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions { + [Collection("Sequential")] [Trait(Traits.OwningTeam, OwningTeam.Fhir)] [Trait(Traits.Category, Categories.Search)] public class FlatteningRewriterTests diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/LastUpdatedToResourceSurrogateIdRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/LastUpdatedToResourceSurrogateIdRewriterTests.cs index 058edaaac3..578f8fcecf 100644 --- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/LastUpdatedToResourceSurrogateIdRewriterTests.cs +++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/LastUpdatedToResourceSurrogateIdRewriterTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions { + [Collection("Sequential")] [Trait(Traits.OwningTeam, OwningTeam.Fhir)] [Trait(Traits.Category, Categories.Search)] public class LastUpdatedToResourceSurrogateIdRewriterTests diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/SqlServerSortingValidatorTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/SqlServerSortingValidatorTests.cs index 9cabbd508a..3d5f664f87 100644 --- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/SqlServerSortingValidatorTests.cs +++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/SqlServerSortingValidatorTests.cs @@ -17,6 +17,7 @@ namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions { + [Collection("Sequential")] [Trait(Traits.OwningTeam, OwningTeam.Fhir)] [Trait(Traits.Category, Categories.Search)] public class SqlServerSortingValidatorTests diff --git a/src/Microsoft.Health.Fhir.SqlServer/Microsoft.Health.Fhir.SqlServer.csproj b/src/Microsoft.Health.Fhir.SqlServer/Microsoft.Health.Fhir.SqlServer.csproj index b1a6e7b04b..4b44b5f9ad 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Microsoft.Health.Fhir.SqlServer.csproj +++ b/src/Microsoft.Health.Fhir.SqlServer/Microsoft.Health.Fhir.SqlServer.csproj @@ -10,10 +10,10 @@ - + - + diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlCustomQueryTests.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlCustomQueryTests.cs index de22234693..9f7631c945 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlCustomQueryTests.cs +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlCustomQueryTests.cs @@ -16,7 +16,6 @@ using Microsoft.Health.Fhir.Tests.Common; using Microsoft.Health.Fhir.Tests.Common.FixtureParameters; using Microsoft.Health.Test.Utilities; -using Microsoft.SqlServer.Management.Sdk.Sfc; using Xunit; using Xunit.Abstractions; using Xunit.Sdk;