diff --git a/.gitignore b/.gitignore index 81551c37dd2..7af2bd181b9 100644 --- a/.gitignore +++ b/.gitignore @@ -327,3 +327,8 @@ stylecop.json in out +# snap{craft} files +parts/ +stage/ +prime/ +*.snap diff --git a/builds/checkin/edgelet.yaml b/builds/checkin/edgelet.yaml index 01ba10292fb..95435d4a7e5 100644 --- a/builds/checkin/edgelet.yaml +++ b/builds/checkin/edgelet.yaml @@ -39,8 +39,16 @@ jobs: displayName: Set env variables - bash: scripts/linux/generic-rust/install.sh --project-root "edgelet" displayName: Install Rust - - bash: scripts/linux/generic-rust/build.sh --project-root "edgelet" --packages "iotedge;aziot-edged" --reduced-linker - displayName: Build + - script: | + source $HOME/.cargo/env + cd edgelet + make \ + CONNECT_MANAGEMENT_URI=unix:///var/run/iotedge/mgmt.sock \ + CONNECT_WORKLOAD_URI=unix:///var/run/iotedge/workload.sock \ + LISTEN_MANAGEMENT_URI=fd://aziot-edged.mgmt.socket \ + LISTEN_WORKLOAD_URI=fd://aziot-edged.workload.socket \ + release + displayName: build - bash: edgelet/build/linux/test.sh displayName: Test diff --git a/builds/e2e/e2e.yaml b/builds/e2e/e2e.yaml index bbe5e0b0f0e..2a865747da0 100644 --- a/builds/e2e/e2e.yaml +++ b/builds/e2e/e2e.yaml @@ -194,6 +194,43 @@ jobs: - template: templates/e2e-clear-docker-cached-images.yaml - template: templates/e2e-run.yaml +################################################################################ + - job: snaps +################################################################################ + displayName: Snaps + + variables: + os: linux + + strategy: + matrix: + amd64: + arch: amd64 + pool: $(pool.linux.name) + image: agent-aziotedge-ubuntu-22.04 + artifactName: iotedged-snap-amd64 + identityServiceArtifactName: packages_snap_amd64 + identityServicePackageFilter: azure-iot-identity_*_amd64.snap + arm64v8: + arch: arm64v8 + pool: $(pool.linux.arm.name) + image: agent-aziotedge-ubuntu-22.04-arm64 + artifactName: iotedged-snap-aarch64 + identityServiceArtifactName: packages_snap_aarch64 + identityServicePackageFilter: azure-iot-identity_*_arm64.snap + + pool: + name: $(pool) + demands: + - ImageOverride -equals $(image) + + steps: + - script: | + sudo snap install docker + displayName: Install Docker as a snap + - template: templates/e2e-setup.yaml + - template: templates/e2e-run.yaml + ################################################################################ - job: centos7_amd64 ################################################################################ diff --git a/builds/e2e/templates/e2e-run.yaml b/builds/e2e/templates/e2e-run.yaml index 9bb52bd0a2d..686f8871fa2 100644 --- a/builds/e2e/templates/e2e-run.yaml +++ b/builds/e2e/templates/e2e-run.yaml @@ -4,13 +4,6 @@ parameters: test_type: '' steps: - # Docker login required for the 'iotedge check' end-to-end test -- task: Docker@2 - displayName: Docker login edgebuilds - inputs: - command: login - containerRegistry: iotedge-edgebuilds-acr - # This E2E test pipeline uses the following filters in order to skip certain tests: # Flaky: Flaky on multiple platforms # FlakyOnArm: Flaky only on arm @@ -119,12 +112,7 @@ steps: Copy-Item "$(binDir)/*-device-*.log" "$logDir/" Copy-Item "$(binDir)/testoutput.log" "$logDir/" Copy-Item "$(binDir)/supportbundle*" "$logDir/" - if ($test_type -eq 'upgrade_scenarios') - { - $artifactSuffix = '$(Build.BuildNumber)-$(System.JobName)' -replace '_','-' - } else { - $artifactSuffix = '$(Build.BuildNumber)-$(System.PhaseName)' -replace '_','-' - } + $artifactSuffix = '$(Build.BuildNumber)-$(System.JobIdentifier)-$(System.JobAttempt)' -replace '_','-' Write-Output "##vso[task.setvariable variable=artifactSuffix]$artifactSuffix" displayName: Collect Logs condition: always() diff --git a/builds/misc/packages-release.yaml b/builds/misc/packages-release.yaml index d5a1fc57734..510021090da 100644 --- a/builds/misc/packages-release.yaml +++ b/builds/misc/packages-release.yaml @@ -20,599 +20,697 @@ resources: stages: ################################################################################ - - stage: BuildPackages +- stage: BuildPackages ################################################################################ - displayName: Build Packages + displayName: Build Packages + pool: + name: $(pool.linux.name) + demands: + - ImageOverride -equals agent-aziotedge-ubuntu-20.04-docker + jobs: + - job: linux + displayName: Linux + strategy: + matrix: + Centos75-amd64: + arch: amd64 + os: centos7 + target.iotedged: edgelet/target/rpmbuild/RPMS/x86_64 + RedHat8-amd64: + arch: amd64 + os: redhat8 + target.iotedged: edgelet/target/rpmbuild/RPMS/x86_64 + RedHat9-amd64: + arch: amd64 + os: redhat9 + target.iotedged: edgelet/target/rpmbuild/RPMS/x86_64 + + Debian10-amd64: + os: debian10 + arch: amd64 + target.iotedged: edgelet/target/release + Debian10-arm32v7: + os: debian10 + arch: arm32v7 + target.iotedged: edgelet/target/armv7-unknown-linux-gnueabihf/release + Debian10-aarch64: + os: debian10 + arch: aarch64 + target.iotedged: edgelet/target/aarch64-unknown-linux-gnu/release + + Debian11-amd64: + os: debian11 + arch: amd64 + target.iotedged: edgelet/target/release + Debian11-arm32v7: + os: debian11 + arch: arm32v7 + target.iotedged: edgelet/target/armv7-unknown-linux-gnueabihf/release + Debian11-aarch64: + os: debian11 + arch: aarch64 + target.iotedged: edgelet/target/aarch64-unknown-linux-gnu/release + + Ubuntu2004-amd64: + arch: amd64 + os: ubuntu20.04 + target.iotedged: edgelet/target/release + Ubuntu2004-arm32v7: + arch: arm32v7 + os: ubuntu20.04 + target.iotedged: edgelet/target/armv7-unknown-linux-gnueabihf/release + Ubuntu2004-aarch64: + arch: aarch64 + os: ubuntu20.04 + target.iotedged: edgelet/target/aarch64-unknown-linux-gnu/release + Ubuntu2204-amd64: + arch: amd64 + os: ubuntu22.04 + target.iotedged: edgelet/target/release + Ubuntu2204-arm32v7: + arch: arm32v7 + os: ubuntu22.04 + target.iotedged: edgelet/target/armv7-unknown-linux-gnueabihf/release + Ubuntu2204-aarch64: + arch: aarch64 + os: ubuntu22.04 + target.iotedged: edgelet/target/aarch64-unknown-linux-gnu/release + steps: + - bash: | + BASE_VERSION=`cat $BUILD_SOURCESDIRECTORY/edgelet/version.txt` + VERSION="$BASE_VERSION" + echo "##vso[task.setvariable variable=VERSION;]$VERSION" + + echo "##vso[task.setvariable variable=PACKAGE_ARCH;]$(arch)" + echo "##vso[task.setvariable variable=PACKAGE_OS;]$(os)" + displayName: Set Version + - script: edgelet/build/linux/package.sh + displayName: Create aziot-edge packages + - task: CopyFiles@2 + displayName: Copy aziot-edge Files to Artifact Staging + inputs: + SourceFolder: $(target.iotedged) + Contents: | + *.deb + *.rpm + TargetFolder: '$(build.artifactstagingdirectory)' + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@3 + displayName: ESRP Binary CodeSigning + inputs: + ConnectedServiceName: "aziotedge-pmc-v4-prod" + FolderPath: '$(build.artifactstagingdirectory)' + Pattern: "*.deb, *.rpm" + SessionTimeout: 20 + inlineOperation: | + [ + { + "KeyCode": "CP-450779-Pgp", + "OperationCode": "LinuxSign", + "Parameters": {}, + "ToolName": "sign", + "toolVersion": "1.0" + } + ] + signConfigType: inlineSignParams + - bash: | + cd $(build.artifactstagingdirectory) + rm -f ./CodeSignSummary* + displayName: Remove CodeSign Summary + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'SBOM Generation Task' + inputs: + BuildDropPath: '$(build.artifactstagingdirectory)' + - task: PublishBuildArtifacts@1 + displayName: Publish Artifacts + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' + ArtifactName: 'iotedged-$(os)-$(arch)' + condition: succeededOrFailed() + + - job: snaps + displayName: Snaps + strategy: + matrix: + amd64: + arch: amd64 + pool: $(pool.linux.name) + image: agent-aziotedge-ubuntu-22.04 + aarch64: + arch: aarch64 + pool: $(pool.linux.arm.name) + image: agent-aziotedge-ubuntu-22.04-arm64 pool: - name: $(pool.linux.name) + name: $(pool) demands: - - ImageOverride -equals agent-aziotedge-ubuntu-20.04-docker - jobs: - - job: linux - displayName: Linux - strategy: - matrix: - Centos75-amd64: - arch: amd64 - os: centos7 - target.iotedged: edgelet/target/rpmbuild/RPMS/x86_64 - RedHat8-amd64: - arch: amd64 - os: redhat8 - target.iotedged: edgelet/target/rpmbuild/RPMS/x86_64 - RedHat9-amd64: - arch: amd64 - os: redhat9 - target.iotedged: edgelet/target/rpmbuild/RPMS/x86_64 - - Debian10-amd64: - os: debian10 - arch: amd64 - target.iotedged: edgelet/target/release - Debian10-arm32v7: - os: debian10 - arch: arm32v7 - target.iotedged: edgelet/target/armv7-unknown-linux-gnueabihf/release - Debian10-aarch64: - os: debian10 - arch: aarch64 - target.iotedged: edgelet/target/aarch64-unknown-linux-gnu/release - - Debian11-amd64: - os: debian11 - arch: amd64 - target.iotedged: edgelet/target/release - Debian11-arm32v7: - os: debian11 - arch: arm32v7 - target.iotedged: edgelet/target/armv7-unknown-linux-gnueabihf/release - Debian11-aarch64: - os: debian11 - arch: aarch64 - target.iotedged: edgelet/target/aarch64-unknown-linux-gnu/release - - Ubuntu2004-amd64: - arch: amd64 - os: ubuntu20.04 - target.iotedged: edgelet/target/release - Ubuntu2004-arm32v7: - arch: arm32v7 - os: ubuntu20.04 - target.iotedged: edgelet/target/armv7-unknown-linux-gnueabihf/release - Ubuntu2004-aarch64: - arch: aarch64 - os: ubuntu20.04 - target.iotedged: edgelet/target/aarch64-unknown-linux-gnu/release - Ubuntu2204-amd64: - arch: amd64 - os: ubuntu22.04 - target.iotedged: edgelet/target/release - Ubuntu2204-arm32v7: - arch: arm32v7 - os: ubuntu22.04 - target.iotedged: edgelet/target/armv7-unknown-linux-gnueabihf/release - Ubuntu2204-aarch64: - arch: aarch64 - os: ubuntu22.04 - target.iotedged: edgelet/target/aarch64-unknown-linux-gnu/release - steps: - - bash: | - BASE_VERSION=`cat $BUILD_SOURCESDIRECTORY/edgelet/version.txt` - VERSION="$BASE_VERSION" - echo "##vso[task.setvariable variable=VERSION;]$VERSION" - - echo "##vso[task.setvariable variable=PACKAGE_ARCH;]$(arch)" - echo "##vso[task.setvariable variable=PACKAGE_OS;]$(os)" - displayName: Set Version - - script: edgelet/build/linux/package.sh - displayName: Create aziot-edge packages - - task: CopyFiles@2 - displayName: Copy aziot-edge Files to Artifact Staging - inputs: - SourceFolder: $(target.iotedged) - Contents: | - *.deb - *.rpm - TargetFolder: '$(build.artifactstagingdirectory)' - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@3 - displayName: ESRP Binary CodeSigning - inputs: - ConnectedServiceName: "aziotedge-pmc-v4-prod" - FolderPath: '$(build.artifactstagingdirectory)' - Pattern: "*.deb, *.rpm" - SessionTimeout: 20 - inlineOperation: | - [ - { - "KeyCode": "CP-450779-Pgp", - "OperationCode": "LinuxSign", - "Parameters": {}, - "ToolName": "sign", - "toolVersion": "1.0" - } - ] - signConfigType: inlineSignParams - - bash: | - cd $(build.artifactstagingdirectory) - rm -f ./CodeSignSummary* - displayName: Remove CodeSign Summary - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 - displayName: 'SBOM Generation Task' - inputs: - BuildDropPath: '$(build.artifactstagingdirectory)' - - task: PublishBuildArtifacts@1 - displayName: Publish Artifacts - inputs: - PathtoPublish: '$(build.artifactstagingdirectory)' - ArtifactName: 'iotedged-$(os)-$(arch)' - condition: succeededOrFailed() + - ImageOverride -equals $(image) + steps: + - script: | + sudo snap install snapcraft --classic + lxd init --minimal + snapcraft --use-lxd + displayName: Build snap + env: + SNAPCRAFT_BUILD_INFO: 1 + - task: CopyFiles@2 + displayName: Stage snap + inputs: + Contents: '*.snap' + SourceFolder: '$(build.sourcesdirectory)' + TargetFolder: '$(build.artifactstagingdirectory)' + - task: PublishBuildArtifacts@1 + displayName: Publish snap as build artifact + inputs: + ArtifactName: 'iotedged-snap-$(arch)' + PathtoPublish: '$(build.artifactstagingdirectory)' ################################################################################ - - stage: PublishPackagesMicrosoft +- stage: PublishPackagesMicrosoft ################################################################################ - displayName: Publish Packages Microsoft - dependsOn: [BuildPackages] - pool: - name: $(pool.linux.name) - demands: - - ImageOverride -equals agent-aziotedge-ubuntu-20.04-docker - jobs: - - deployment: safe_guard - environment: 'Azure-IoT-Edge-Core Release Env' - displayName: Get Approval - strategy: - runOnce: - deploy: - steps: - - bash: | - echo "Approval Complete" - - job: linux - displayName: Linux - strategy: - matrix: - # For ARM32 Debian10, we solely publishing iot-identity-service artifacts - # for the partner teams. We don't fully support edgelet upload because - # there is a known docker issue with debian 10. - Debian10-arm32v7: - excludeEdgelet: true - os: debian10 - artifactName: iotedged-debian10-arm32v7 - identityServiceArtifactName: packages_debian-10-slim_arm32v7 - identityServicePackageFilter: aziot-identity-service_*_armhf.deb - pmcRepoName: microsoft-debian-buster-prod-apt - pmcRelease: buster - Debian11-arm32v7: - os: debian11 - artifactName: iotedged-debian11-arm32v7 - identityServiceArtifactName: packages_debian-11-slim_arm32v7 - identityServicePackageFilter: aziot-identity-service_*_armhf.deb - pmcRepoName: microsoft-debian-bullseye-prod-apt - pmcRelease: bullseye - - RedHat8-amd64: - os: redhat8 - artifactName: iotedged-redhat8-amd64 - identityServiceArtifactName: packages_redhat-ubi8-latest_amd64 - identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm - pmcRepoName: microsoft-rhel8.0-prod-yum - pmcRelease: '' - RedHat9-amd64: - os: redhat9 - artifactName: iotedged-redhat9-amd64 - identityServiceArtifactName: packages_redhat-ubi9-latest_amd64 - identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm - pmcRepoName: microsoft-rhel9.0-prod-yum - pmcRelease: '' - - Ubuntu2004-amd64: - os: ubuntu20.04 - artifactName: iotedged-ubuntu20.04-amd64 - identityServiceArtifactName: packages_ubuntu-20.04_amd64 - identityServicePackageFilter: aziot-identity-service_*_amd64.deb - pmcRepoName: microsoft-ubuntu-focal-prod-apt - pmcRelease: focal - Ubuntu2004-aarch64: - os: ubuntu20.04 - artifactName: iotedged-ubuntu20.04-aarch64 - identityServiceArtifactName: packages_ubuntu-20.04_aarch64 - identityServicePackageFilter: aziot-identity-service_*_arm64.deb - pmcRepoName: microsoft-ubuntu-focal-prod-apt - pmcRelease: focal - - Ubuntu2204-amd64: - os: ubuntu22.04 - artifactName: iotedged-ubuntu22.04-amd64 - identityServiceArtifactName: packages_ubuntu-22.04_amd64 - identityServicePackageFilter: aziot-identity-service_*_amd64.deb - pmcRepoName: microsoft-ubuntu-jammy-prod-apt - pmcRelease: jammy - Ubuntu2204-aarch64: - os: ubuntu22.04 - artifactName: iotedged-ubuntu22.04-aarch64 - identityServiceArtifactName: packages_ubuntu-22.04_aarch64 - identityServicePackageFilter: aziot-identity-service_*_arm64.deb - pmcRepoName: microsoft-ubuntu-jammy-prod-apt - pmcRelease: jammy - - steps: - - task: AzureKeyVault@1 - displayName: Get secrets - inputs: - azureSubscription: $(az.subscription) - keyVaultName: $(kv.name) - secretsFilter: >- - GitHubAccessToken - - checkout: self - - task: DownloadPipelineArtifact@2 - displayName: Download Pipeline Build Packages - condition: not(eq(variables.excludeEdgelet, true)) - inputs: - source: 'current' - path: $(System.ArtifactsDirectory) - patterns: | - $(artifactName)/*.deb - $(artifactName)/*.rpm - - task: PowerShell@2 - displayName: 'Download aziot-identity-service' - inputs: - filePath: $(Build.SourcesDirectory)/scripts/local/test/DownloadIdentityService.ps1 - workingDirectory: $(Build.SourcesDirectory) - env: - GITHUB_TOKEN: $(GitHubAccessToken) - ARTIFACT_NAME: $(identityServiceArtifactName) - PACKAGE_FILTER: $(identityServicePackageFilter) - DOWNLOAD_PATH: $(System.ArtifactsDirectory)/$(artifactName) - IDENTITY_SERVICE_COMMIT: $(aziotis.commit) - - task: AzureCLI@2 - displayName: Setup Package Publisher - inputs: - azureSubscription: $(az.subscription) - scriptType: bash - scriptPath: $(Build.SourcesDirectory)/scripts/linux/publishReleasePackages.sh - arguments: --setup-pmc-only true -w "$(System.ArtifactsDirectory)" -d "$(System.ArtifactsDirectory)/$(artifactName)" - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@3 - displayName: ESRP Binary CodeSigning (IIS) - inputs: - ConnectedServiceName: "aziotedge-pmc-v4-prod" - FolderPath: '$(System.ArtifactsDirectory)/$(artifactName)' - Pattern: "*.deb, *.rpm" - SessionTimeout: 20 - inlineOperation: | - [ - { - "KeyCode": "CP-450779-Pgp", - "OperationCode": "LinuxSign", - "Parameters": {}, - "ToolName": "sign", - "toolVersion": "1.0" - } - ] - signConfigType: inlineSignParams - - bash: | - cd "$(System.ArtifactsDirectory)/$(artifactName)" - rm -f ./CodeSignSummary* - displayName: Remove CodeSign Summary - - task: PublishBuildArtifacts@1 - displayName: Publish Artifacts - inputs: - PathtoPublish: '$(System.ArtifactsDirectory)/$(artifactName)' - ArtifactName: '$(artifactName)' - condition: succeededOrFailed() - - script: | - $(Build.SourcesDirectory)/scripts/linux/publishReleasePackages.sh \ - -p "$(os)" \ - -w "$(System.ArtifactsDirectory)" \ - -d "$(System.ArtifactsDirectory)/$(artifactName)" \ - -s "$(package-server-name)" \ - --pmc-repository "$(pmcRepoName)" \ - --pmc-release "$(pmcRelease)" - displayName: PMC Package Publication + displayName: Publish Packages Microsoft + dependsOn: [BuildPackages] + pool: + name: $(pool.linux.name) + demands: + - ImageOverride -equals agent-aziotedge-ubuntu-20.04-docker + jobs: + - deployment: safe_guard + environment: 'Azure-IoT-Edge-Core Release Env' + displayName: Get Approval + strategy: + runOnce: + deploy: + steps: + - bash: | + echo "Approval Complete" + - job: linux + displayName: Linux + strategy: + matrix: + # For ARM32 Debian10, we solely publishing iot-identity-service artifacts + # for the partner teams. We don't fully support edgelet upload because + # there is a known docker issue with debian 10. + Debian10-arm32v7: + excludeEdgelet: true + os: debian10 + artifactName: iotedged-debian10-arm32v7 + identityServiceArtifactName: packages_debian-10-slim_arm32v7 + identityServicePackageFilter: aziot-identity-service_*_armhf.deb + pmcRepoName: microsoft-debian-buster-prod-apt + pmcRelease: buster + Debian11-arm32v7: + os: debian11 + artifactName: iotedged-debian11-arm32v7 + identityServiceArtifactName: packages_debian-11-slim_arm32v7 + identityServicePackageFilter: aziot-identity-service_*_armhf.deb + pmcRepoName: microsoft-debian-bullseye-prod-apt + pmcRelease: bullseye + + RedHat8-amd64: + os: redhat8 + artifactName: iotedged-redhat8-amd64 + identityServiceArtifactName: packages_redhat-ubi8-latest_amd64 + identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm + pmcRepoName: microsoft-rhel8.0-prod-yum + pmcRelease: '' + RedHat9-amd64: + os: redhat9 + artifactName: iotedged-redhat9-amd64 + identityServiceArtifactName: packages_redhat-ubi9-latest_amd64 + identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm + pmcRepoName: microsoft-rhel9.0-prod-yum + pmcRelease: '' + + Ubuntu2004-amd64: + os: ubuntu20.04 + artifactName: iotedged-ubuntu20.04-amd64 + identityServiceArtifactName: packages_ubuntu-20.04_amd64 + identityServicePackageFilter: aziot-identity-service_*_amd64.deb + pmcRepoName: microsoft-ubuntu-focal-prod-apt + pmcRelease: focal + Ubuntu2004-aarch64: + os: ubuntu20.04 + artifactName: iotedged-ubuntu20.04-aarch64 + identityServiceArtifactName: packages_ubuntu-20.04_aarch64 + identityServicePackageFilter: aziot-identity-service_*_arm64.deb + pmcRepoName: microsoft-ubuntu-focal-prod-apt + pmcRelease: focal + + Ubuntu2204-amd64: + os: ubuntu22.04 + artifactName: iotedged-ubuntu22.04-amd64 + identityServiceArtifactName: packages_ubuntu-22.04_amd64 + identityServicePackageFilter: aziot-identity-service_*_amd64.deb + pmcRepoName: microsoft-ubuntu-jammy-prod-apt + pmcRelease: jammy + Ubuntu2204-aarch64: + os: ubuntu22.04 + artifactName: iotedged-ubuntu22.04-aarch64 + identityServiceArtifactName: packages_ubuntu-22.04_aarch64 + identityServicePackageFilter: aziot-identity-service_*_arm64.deb + pmcRepoName: microsoft-ubuntu-jammy-prod-apt + pmcRelease: jammy + + steps: + - task: AzureKeyVault@1 + displayName: Get secrets + inputs: + azureSubscription: $(az.subscription) + keyVaultName: $(kv.name) + secretsFilter: >- + GitHubAccessToken + - checkout: self + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Build Packages + condition: not(eq(variables.excludeEdgelet, true)) + inputs: + source: 'current' + path: $(System.ArtifactsDirectory) + patterns: | + $(artifactName)/*.deb + $(artifactName)/*.rpm + - task: PowerShell@2 + displayName: 'Download aziot-identity-service' + inputs: + filePath: $(Build.SourcesDirectory)/scripts/local/test/DownloadIdentityService.ps1 + workingDirectory: $(Build.SourcesDirectory) + env: + GITHUB_TOKEN: $(GitHubAccessToken) + ARTIFACT_NAME: $(identityServiceArtifactName) + PACKAGE_FILTER: $(identityServicePackageFilter) + DOWNLOAD_PATH: $(System.ArtifactsDirectory)/$(artifactName) + IDENTITY_SERVICE_COMMIT: $(aziotis.commit) + - task: AzureCLI@2 + displayName: Setup Package Publisher + inputs: + azureSubscription: $(az.subscription) + scriptType: bash + scriptPath: $(Build.SourcesDirectory)/scripts/linux/publishReleasePackages.sh + arguments: --setup-pmc-only true -w "$(System.ArtifactsDirectory)" -d "$(System.ArtifactsDirectory)/$(artifactName)" + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@3 + displayName: ESRP Binary CodeSigning (IIS) + inputs: + ConnectedServiceName: "aziotedge-pmc-v4-prod" + FolderPath: '$(System.ArtifactsDirectory)/$(artifactName)' + Pattern: "*.deb, *.rpm" + SessionTimeout: 20 + inlineOperation: | + [ + { + "KeyCode": "CP-450779-Pgp", + "OperationCode": "LinuxSign", + "Parameters": {}, + "ToolName": "sign", + "toolVersion": "1.0" + } + ] + signConfigType: inlineSignParams + - bash: | + cd "$(System.ArtifactsDirectory)/$(artifactName)" + rm -f ./CodeSignSummary* + displayName: Remove CodeSign Summary + - task: PublishBuildArtifacts@1 + displayName: Publish Artifacts + inputs: + PathtoPublish: '$(System.ArtifactsDirectory)/$(artifactName)' + ArtifactName: '$(artifactName)' + condition: succeededOrFailed() + - script: | + $(Build.SourcesDirectory)/scripts/linux/publishReleasePackages.sh \ + -p "$(os)" \ + -w "$(System.ArtifactsDirectory)" \ + -d "$(System.ArtifactsDirectory)/$(artifactName)" \ + -s "$(package-server-name)" \ + --pmc-repository "$(pmcRepoName)" \ + --pmc-release "$(pmcRelease)" + displayName: PMC Package Publication + + - job: snaps + displayName: Snaps + steps: + - task: AzureKeyVault@1 + displayName: Get secrets + inputs: + azureSubscription: $(az.subscription) + keyVaultName: $(kv.name) + secretsFilter: >- + GitHubAccessToken, + snapcraft-store-credentials + - task: DownloadPipelineArtifact@2 + displayName: Get azure-iot-edge snaps + inputs: + source: current + path: '$(System.ArtifactsDirectory)' + patterns: 'iotedged-snap-*/*.snap' + - pwsh: | + foreach ($arch in @('amd64', 'aarch64')) + { + $env:ARTIFACT_NAME = "packages_snap_$arch" + $env:PACKAGE_FILTER = "azure-iot-identity_*_$($arch -eq 'aarch64' ? 'arm64' : $arch).snap" + $env:DOWNLOAD_PATH = "$(System.ArtifactsDirectory)/packages_snap_$arch" + New-Item $env:DOWNLOAD_PATH -ItemType Directory -Force | Out-Null + scripts/local/test/DownloadIdentityService.ps1 + } + displayName: Get azure-iot-identity snaps + env: + GITHUB_TOKEN: '$(GitHubAccessToken)' + IDENTITY_SERVICE_COMMIT: '$(aziotis.commit)' + - script: | + sudo snap install snapcraft --classic + for pkg in $(ls $(System.ArtifactsDirectory)/**/*.snap) + do + # TODO: change release channel from 'edge' to 'stable' + snapcraft upload --release=edge "$pkg" + done + + logdir='$(Build.ArtifactStagingDirectory)/logs' + mkdir -p "$logdir" + cp $HOME/.local/state/snapcraft/log/*.log "$logdir/" + displayName: Publish snaps to snapcraft + env: + SNAPCRAFT_STORE_CREDENTIALS: '$(snapcraft-store-credentials)' + - task: PublishBuildArtifacts@1 + displayName: Save snapcraft logs + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/logs' + ArtifactName: 'snapcraft-logs' + condition: succeededOrFailed() ############################################################################## - - stage: PublishPackagesGithub +- stage: PublishPackagesGithub ############################################################################## - displayName: Publish Packages Github - dependsOn: [BuildPackages] - pool: - name: $(pool.linux.name) - demands: - - ImageOverride -equals agent-aziotedge-ubuntu-20.04-docker - jobs: - - deployment: safe_guard - environment: 'Azure-IoT-Edge-Core Release Env' - displayName: Get Approval - strategy: - runOnce: - deploy: - steps: - - task: AzureKeyVault@1 - displayName: Get secrets - inputs: - azureSubscription: $(az.subscription) - keyVaultName: $(kv.name) - secretsFilter: >- - GitHubAccessToken - - checkout: self - - checkout: azure-iotedge - submodules: recursive - - checkout: iot-identity-service - submodules: recursive - - bash: | #Create Release Page Before Publishing Artifacts in Parallel in the Next Job. - echo "Approval Complete" - BASE_VERSION="$(cat $BUILD_SOURCESDIRECTORY/iotedge/edgelet/version.txt)" - VERSION="$BASE_VERSION" - AZURE_IOTEDGE_REPO_PATH="$(Build.SourcesDirectory)/azure-iotedge" - IOTEDGE_REPO_PATH="$(Build.SourcesDirectory)/iotedge" - IIS_REPO_PATH="$(Build.SourcesDirectory)/iot-identity-service" - BRANCH_NAME="$(Build.SourceBranch)" - echo "Version: $VERSION" - echo "Branch name: $BRANCH_NAME" - echo "azure-iotedge repo path: $AZURE_IOTEDGE_REPO_PATH" - echo "iotedge repo path: $IOTEDGE_REPO_PATH" - echo "iot-identity-service repo path: $IIS_REPO_PATH" - - $(Build.SourcesDirectory)/iotedge/scripts/linux/publishReleasePackages.sh -p ubuntu20.04 -w $(Build.SourcesDirectory)/iotedge -d $(Build.SourcesDirectory)/iotedge -v "$BASE_VERSION" -s "github.com" -b $(Build.SourceBranch) --skip-upload true - - # Source the scripts & Update version files - source $(Build.SourcesDirectory)/iotedge/scripts/linux/github/updateLatestVersion.sh - update_latest_version_json - - # Update Github and tag - github_update_and_push - env: - GITHUB_PAT: "$(GitHubAccessToken)" - - job: linux - displayName: Linux - dependsOn: safe_guard - strategy: - matrix: - Centos75-amd64: - os: centos7 - artifactName: iotedged-centos7-amd64 - identityServiceArtifactName: packages_centos-7_amd64 - identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm - RedHat8-amd64: - os: redhat8 - artifactName: iotedged-redhat8-amd64 - identityServiceArtifactName: packages_redhat-ubi8-latest_amd64 - identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm - RedHat9-amd64: - os: redhat9 - artifactName: iotedged-redhat9-amd64 - identityServiceArtifactName: packages_redhat-ubi9-latest_amd64 - identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm - - Debian10-amd64: - os: debian10 - artifactName: iotedged-debian10-amd64 - identityServiceArtifactName: packages_debian-10-slim_amd64 - identityServicePackageFilter: aziot-identity-service_*_amd64.deb - Debian10-arm32v7: - os: debian10 - artifactName: iotedged-debian10-arm32v7 - identityServiceArtifactName: packages_debian-10-slim_arm32v7 - identityServicePackageFilter: aziot-identity-service_*_armhf.deb - Debian10-aarch64: - os: debian10 - artifactName: iotedged-debian10-aarch64 - identityServiceArtifactName: packages_debian-10-slim_aarch64 - identityServicePackageFilter: aziot-identity-service_*_arm64.deb - - Debian11-amd64: - os: debian11 - artifactName: iotedged-debian11-amd64 - identityServiceArtifactName: packages_debian-11-slim_amd64 - identityServicePackageFilter: aziot-identity-service_*_amd64.deb - Debian11-arm32v7: - os: debian11 - artifactName: iotedged-debian11-arm32v7 - identityServiceArtifactName: packages_debian-11-slim_arm32v7 - identityServicePackageFilter: aziot-identity-service_*_armhf.deb - Debian11-aarch64: - os: debian11 - artifactName: iotedged-debian11-aarch64 - identityServiceArtifactName: packages_debian-11-slim_aarch64 - identityServicePackageFilter: aziot-identity-service_*_arm64.deb - - Ubuntu2004-amd64: - os: ubuntu20.04 - artifactName: iotedged-ubuntu20.04-amd64 - identityServiceArtifactName: packages_ubuntu-20.04_amd64 - identityServicePackageFilter: aziot-identity-service_*_amd64.deb - Ubuntu2004-arm32v7: - os: ubuntu20.04 - artifactName: iotedged-ubuntu20.04-arm32v7 - identityServiceArtifactName: packages_ubuntu-20.04_arm32v7 - identityServicePackageFilter: aziot-identity-service_*_armhf.deb - Ubuntu2004-aarch64: - os: ubuntu20.04 - artifactName: iotedged-ubuntu20.04-aarch64 - identityServiceArtifactName: packages_ubuntu-20.04_aarch64 - identityServicePackageFilter: aziot-identity-service_*_arm64.deb - - Ubuntu2204-amd64: - os: ubuntu22.04 - artifactName: iotedged-ubuntu22.04-amd64 - identityServiceArtifactName: packages_ubuntu-22.04_amd64 - identityServicePackageFilter: aziot-identity-service_*_amd64.deb - Ubuntu2204-arm32v7: - os: ubuntu22.04 - artifactName: iotedged-ubuntu22.04-arm32v7 - identityServiceArtifactName: packages_ubuntu-22.04_arm32v7 - identityServicePackageFilter: aziot-identity-service_*_armhf.deb - Ubuntu2204-aarch64: - os: ubuntu22.04 - artifactName: iotedged-ubuntu22.04-aarch64 - identityServiceArtifactName: packages_ubuntu-22.04_aarch64 - identityServicePackageFilter: aziot-identity-service_*_arm64.deb - - steps: - - task: AzureKeyVault@1 - displayName: Get secrets - inputs: - azureSubscription: $(az.subscription) - keyVaultName: $(kv.name) - secretsFilter: >- - GitHubAccessToken - - checkout: self - - task: DownloadPipelineArtifact@2 - displayName: Download Pipeline Build Packages - inputs: - source: 'current' - path: $(System.ArtifactsDirectory) - patterns: | - $(artifactName)/*.deb - $(artifactName)/*.rpm - - task: PowerShell@2 - displayName: 'Download aziot-identity-service' - inputs: - filePath: $(Build.SourcesDirectory)/scripts/local/test/DownloadIdentityService.ps1 - workingDirectory: $(Build.SourcesDirectory) - env: - GITHUB_TOKEN: $(GitHubAccessToken) - ARTIFACT_NAME: $(identityServiceArtifactName) - PACKAGE_FILTER: $(identityServicePackageFilter) - DOWNLOAD_PATH: $(System.ArtifactsDirectory)/$(artifactName) - IDENTITY_SERVICE_COMMIT: $(aziotis.commit) - - bash: | - BASE_VERSION=`cat $BUILD_SOURCESDIRECTORY/edgelet/version.txt` - $(Build.SourcesDirectory)/scripts/linux/publishReleasePackages.sh \ - -p $(os) \ - -w $(System.ArtifactsDirectory) \ - -d $(System.ArtifactsDirectory)/$(artifactName) \ - -v "$BASE_VERSION" \ - -s "github.com" \ - -b $(Build.SourceBranch) - env: - GITHUB_PAT: "$(GitHubAccessToken)" - name: publish_artifacts + displayName: Publish Packages Github + dependsOn: [BuildPackages] + pool: + name: $(pool.linux.name) + demands: + - ImageOverride -equals agent-aziotedge-ubuntu-20.04-docker + jobs: + - deployment: safe_guard + environment: 'Azure-IoT-Edge-Core Release Env' + displayName: Get Approval + strategy: + runOnce: + deploy: + steps: + - task: AzureKeyVault@1 + displayName: Get secrets + inputs: + azureSubscription: $(az.subscription) + keyVaultName: $(kv.name) + secretsFilter: >- + GitHubAccessToken + - checkout: self + - checkout: azure-iotedge + submodules: recursive + - checkout: iot-identity-service + submodules: recursive + - bash: | #Create Release Page Before Publishing Artifacts in Parallel in the Next Job. + echo "Approval Complete" + BASE_VERSION="$(cat $BUILD_SOURCESDIRECTORY/iotedge/edgelet/version.txt)" + VERSION="$BASE_VERSION" + AZURE_IOTEDGE_REPO_PATH="$(Build.SourcesDirectory)/azure-iotedge" + IOTEDGE_REPO_PATH="$(Build.SourcesDirectory)/iotedge" + IIS_REPO_PATH="$(Build.SourcesDirectory)/iot-identity-service" + BRANCH_NAME="$(Build.SourceBranch)" + echo "Version: $VERSION" + echo "Branch name: $BRANCH_NAME" + echo "azure-iotedge repo path: $AZURE_IOTEDGE_REPO_PATH" + echo "iotedge repo path: $IOTEDGE_REPO_PATH" + echo "iot-identity-service repo path: $IIS_REPO_PATH" + + $(Build.SourcesDirectory)/iotedge/scripts/linux/publishReleasePackages.sh -p ubuntu20.04 -w $(Build.SourcesDirectory)/iotedge -d $(Build.SourcesDirectory)/iotedge -v "$BASE_VERSION" -s "github.com" -b $(Build.SourceBranch) --skip-upload true + + # Source the scripts & Update version files + source $(Build.SourcesDirectory)/iotedge/scripts/linux/github/updateLatestVersion.sh + update_latest_version_json + + # Update Github and tag + github_update_and_push + env: + GITHUB_PAT: "$(GitHubAccessToken)" + - job: linux + displayName: Linux + dependsOn: safe_guard + strategy: + matrix: + Centos75-amd64: + os: centos7 + artifactName: iotedged-centos7-amd64 + identityServiceArtifactName: packages_centos-7_amd64 + identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm + RedHat8-amd64: + os: redhat8 + artifactName: iotedged-redhat8-amd64 + identityServiceArtifactName: packages_redhat-ubi8-latest_amd64 + identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm + RedHat9-amd64: + os: redhat9 + artifactName: iotedged-redhat9-amd64 + identityServiceArtifactName: packages_redhat-ubi9-latest_amd64 + identityServicePackageFilter: aziot-identity-service-*.x86_64.rpm + + Debian10-amd64: + os: debian10 + artifactName: iotedged-debian10-amd64 + identityServiceArtifactName: packages_debian-10-slim_amd64 + identityServicePackageFilter: aziot-identity-service_*_amd64.deb + Debian10-arm32v7: + os: debian10 + artifactName: iotedged-debian10-arm32v7 + identityServiceArtifactName: packages_debian-10-slim_arm32v7 + identityServicePackageFilter: aziot-identity-service_*_armhf.deb + Debian10-aarch64: + os: debian10 + artifactName: iotedged-debian10-aarch64 + identityServiceArtifactName: packages_debian-10-slim_aarch64 + identityServicePackageFilter: aziot-identity-service_*_arm64.deb + + Debian11-amd64: + os: debian11 + artifactName: iotedged-debian11-amd64 + identityServiceArtifactName: packages_debian-11-slim_amd64 + identityServicePackageFilter: aziot-identity-service_*_amd64.deb + Debian11-arm32v7: + os: debian11 + artifactName: iotedged-debian11-arm32v7 + identityServiceArtifactName: packages_debian-11-slim_arm32v7 + identityServicePackageFilter: aziot-identity-service_*_armhf.deb + Debian11-aarch64: + os: debian11 + artifactName: iotedged-debian11-aarch64 + identityServiceArtifactName: packages_debian-11-slim_aarch64 + identityServicePackageFilter: aziot-identity-service_*_arm64.deb + + Ubuntu2004-amd64: + os: ubuntu20.04 + artifactName: iotedged-ubuntu20.04-amd64 + identityServiceArtifactName: packages_ubuntu-20.04_amd64 + identityServicePackageFilter: aziot-identity-service_*_amd64.deb + Ubuntu2004-arm32v7: + os: ubuntu20.04 + artifactName: iotedged-ubuntu20.04-arm32v7 + identityServiceArtifactName: packages_ubuntu-20.04_arm32v7 + identityServicePackageFilter: aziot-identity-service_*_armhf.deb + Ubuntu2004-aarch64: + os: ubuntu20.04 + artifactName: iotedged-ubuntu20.04-aarch64 + identityServiceArtifactName: packages_ubuntu-20.04_aarch64 + identityServicePackageFilter: aziot-identity-service_*_arm64.deb + + Ubuntu2204-amd64: + os: ubuntu22.04 + artifactName: iotedged-ubuntu22.04-amd64 + identityServiceArtifactName: packages_ubuntu-22.04_amd64 + identityServicePackageFilter: aziot-identity-service_*_amd64.deb + Ubuntu2204-arm32v7: + os: ubuntu22.04 + artifactName: iotedged-ubuntu22.04-arm32v7 + identityServiceArtifactName: packages_ubuntu-22.04_arm32v7 + identityServicePackageFilter: aziot-identity-service_*_armhf.deb + Ubuntu2204-aarch64: + os: ubuntu22.04 + artifactName: iotedged-ubuntu22.04-aarch64 + identityServiceArtifactName: packages_ubuntu-22.04_aarch64 + identityServicePackageFilter: aziot-identity-service_*_arm64.deb + + Snap-amd64: + os: ubuntu22.04 + artifactName: iotedged-snap-amd64 + identityServiceArtifactName: packages_snap_amd64 + identityServicePackageFilter: azure-iot-identity_*_amd64.snap + Snap-aarch64: + os: ubuntu22.04 + artifactName: iotedged-snap-aarch64 + identityServiceArtifactName: packages_snap_aarch64 + identityServicePackageFilter: azure-iot-identity_*_arm64.snap + + steps: + - task: AzureKeyVault@1 + displayName: Get secrets + inputs: + azureSubscription: $(az.subscription) + keyVaultName: $(kv.name) + secretsFilter: >- + GitHubAccessToken + - checkout: self + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Build Packages + inputs: + source: 'current' + path: $(System.ArtifactsDirectory) + patterns: | + $(artifactName)/*.deb + $(artifactName)/*.rpm + $(artifactName)/*.snap + - task: PowerShell@2 + displayName: 'Download aziot-identity-service' + inputs: + filePath: $(Build.SourcesDirectory)/scripts/local/test/DownloadIdentityService.ps1 + workingDirectory: $(Build.SourcesDirectory) + env: + GITHUB_TOKEN: $(GitHubAccessToken) + ARTIFACT_NAME: $(identityServiceArtifactName) + PACKAGE_FILTER: $(identityServicePackageFilter) + DOWNLOAD_PATH: $(System.ArtifactsDirectory)/$(artifactName) + IDENTITY_SERVICE_COMMIT: $(aziotis.commit) + - bash: | + BASE_VERSION=`cat $BUILD_SOURCESDIRECTORY/edgelet/version.txt` + $(Build.SourcesDirectory)/scripts/linux/publishReleasePackages.sh \ + -p $(os) \ + -w $(System.ArtifactsDirectory) \ + -d $(System.ArtifactsDirectory)/$(artifactName) \ + -v "$BASE_VERSION" \ + -s "github.com" \ + -b $(Build.SourceBranch) + env: + GITHUB_PAT: "$(GitHubAccessToken)" + name: publish_artifacts ############################################################################## - - stage: smokeTests +- stage: smokeTests ############################################################################## - displayName: Post-release Smoke Tests - dependsOn: - - PublishPackagesMicrosoft - - PublishPackagesGithub - jobs: - - job: smokeTests - displayName: Release Artifact Smoke Tests - strategy: - matrix: - Ubuntu2204-amd64: - pool_name: 'Azure-IoT-Edge-1ES-Hosted-Linux' - agent_demands: 'ImageOverride -equals agent-aziotedge-ubuntu-22.04-msmoby' - os: ubuntu22.04 - arch: amd64 - ext: deb - Ubuntu2204-arm64: - pool_name: 'Azure-IoT-Edge-1ES-Hosted-Linux-Arm64' - agent_demands: 'ImageOverride -equals agent-aziotedge-ubuntu-22.04-arm64-msmoby' - os: ubuntu22.04 - arch: arm64 - ext: deb - - pool: - name: $(pool_name) - demands: - - $(agent_demands) - - steps: - - checkout: azure-iotedge - - checkout: self - - task: AzureKeyVault@1 - displayName: Get secrets - inputs: - azureSubscription: $(az.subscription) - keyVaultName: $(kv.name) - secretsFilter: >- - IotEdge1-PAT-msazure - - - bash: | - # Source the scripts & Update version files - source $(Build.SourcesDirectory)/iotedge/scripts/linux/smokeTestHelper.sh - - wait-for-dpkg-lock 120 - - if ! command -v jq &> /dev/null - then - sudo apt-get install jq -y - fi - displayName: Setup Test Agent - name: SetupTestAgent - - - bash: | - # Fetch versions to be used for each component - edgeletVersion=$(cat $(Build.SourcesDirectory)/azure-iotedge/latest-aziot-edge.json | jq ".\"aziot-edge\"" | tr -d '"') - iisVersion=$(cat cat $(Build.SourcesDirectory)/azure-iotedge/latest-aziot-identity-service.json | jq ".\"aziot-identity-service\"" | tr -d '"') - - echo "##vso[task.setvariable variable=edgeletVersion;isOutput=true]$edgeletVersion" - echo "##vso[task.setvariable variable=iisVersion;isOutput=true]$iisVersion" - env: - GITHUB_PAT: "$(GitHubAccessToken)" - displayName: Get Parameters From Azure-iotedge - name: GitHubParameters - - - bash: | - edgeletVersion=$(GitHubParameters.edgeletVersion) - iisVersion=$(GitHubParameters.iisVersion) - - wget https://github.com/Azure/azure-iotedge/releases/download/$edgeletVersion/aziot-edge_$edgeletVersion-1_$(os)_$(arch).$(ext) -O $(System.ArtifactsDirectory)/aziot-edge_$edgeletVersion-1_$(os)_$(arch)_github.$(ext) - wget https://github.com/Azure/azure-iotedge/releases/download/$edgeletVersion/aziot-identity-service_$iisVersion-1_$(os)_$(arch).$(ext) -O $(System.ArtifactsDirectory)/aziot-identity-service_$iisVersion-1_$(os)_$(arch)_github.$(ext) - displayName: Download Edgelet GitHub - - - bash: | - # Source the scripts & Update version files - source $(Build.SourcesDirectory)/iotedge/scripts/linux/smokeTestHelper.sh - - if [[ "$(os)" == "ubuntu"* ]]; then - # Ubuntu - if [[ "$(os)" == *"22.04" ]]; then - setup-config-apt "https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb" - elif [[ "$(os)" == *"20.04" ]]; then - setup-focal-source-apt - setup-config-apt "https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb" - else - echo "Unsupported OS: $(os)" - exit 1; - fi - elif [[ "$(os)" == "debian11" ]]; then - setup-config-apt "https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb" + displayName: Post-release Smoke Tests + dependsOn: + - PublishPackagesMicrosoft + - PublishPackagesGithub + jobs: + - job: smokeTests + displayName: Release Artifact Smoke Tests + strategy: + matrix: + Ubuntu2204-amd64: + pool_name: 'Azure-IoT-Edge-1ES-Hosted-Linux' + agent_demands: 'ImageOverride -equals agent-aziotedge-ubuntu-22.04-msmoby' + os: ubuntu22.04 + arch: amd64 + ext: deb + Ubuntu2204-arm64: + pool_name: 'Azure-IoT-Edge-1ES-Hosted-Linux-Arm64' + agent_demands: 'ImageOverride -equals agent-aziotedge-ubuntu-22.04-arm64-msmoby' + os: ubuntu22.04 + arch: arm64 + ext: deb + + pool: + name: $(pool_name) + demands: + - $(agent_demands) + + steps: + - checkout: azure-iotedge + - checkout: self + - task: AzureKeyVault@1 + displayName: Get secrets + inputs: + azureSubscription: $(az.subscription) + keyVaultName: $(kv.name) + secretsFilter: >- + IotEdge1-PAT-msazure + + - bash: | + # Source the scripts & Update version files + source $(Build.SourcesDirectory)/iotedge/scripts/linux/smokeTestHelper.sh + + wait-for-dpkg-lock 120 + + if ! command -v jq &> /dev/null + then + sudo apt-get install jq -y + fi + displayName: Setup Test Agent + + - bash: | + # Fetch versions to be used for each component + edgeletVersion=$(cat $(Build.SourcesDirectory)/azure-iotedge/latest-aziot-edge.json | jq ".\"aziot-edge\"" | tr -d '"') + iisVersion=$(cat cat $(Build.SourcesDirectory)/azure-iotedge/latest-aziot-identity-service.json | jq ".\"aziot-identity-service\"" | tr -d '"') + + echo "##vso[task.setvariable variable=edgeletVersion;isOutput=true]$edgeletVersion" + echo "##vso[task.setvariable variable=iisVersion;isOutput=true]$iisVersion" + env: + GITHUB_PAT: "$(GitHubAccessToken)" + displayName: Get Parameters From Azure-iotedge + name: GitHubParameters + + - bash: | + edgeletVersion=$(GitHubParameters.edgeletVersion) + iisVersion=$(GitHubParameters.iisVersion) + + wget https://github.com/Azure/azure-iotedge/releases/download/$edgeletVersion/aziot-edge_$edgeletVersion-1_$(os)_$(arch).$(ext) -O $(System.ArtifactsDirectory)/aziot-edge_$edgeletVersion-1_$(os)_$(arch)_github.$(ext) + wget https://github.com/Azure/azure-iotedge/releases/download/$edgeletVersion/aziot-identity-service_$iisVersion-1_$(os)_$(arch).$(ext) -O $(System.ArtifactsDirectory)/aziot-identity-service_$iisVersion-1_$(os)_$(arch)_github.$(ext) + displayName: Download Edgelet GitHub + + - bash: | + # Source the scripts & Update version files + source $(Build.SourcesDirectory)/iotedge/scripts/linux/smokeTestHelper.sh + + if [[ "$(os)" == "ubuntu"* ]]; then + # Ubuntu + if [[ "$(os)" == *"22.04" ]]; then + setup-config-apt "https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb" + elif [[ "$(os)" == *"20.04" ]]; then + setup-focal-source-apt + setup-config-apt "https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb" else echo "Unsupported OS: $(os)" exit 1; fi - - echo $'\n\n================================================\n\n' - echo "Aziot-edge" - test-released-metadata "aziot-edge" "$(GitHubParameters.edgeletVersion)" - - test-released-artifact \ - "aziot-edge" \ - "$(GitHubParameters.edgeletVersion)" \ - "$(System.ArtifactsDirectory)" \ - "$(GitHubParameters.edgeletVersion)-1_$(os)_$(arch)_github.$(ext)" \ - "$(edgelet.maxPercentAllowed)" \ - "$(IsCheckPreviousPkg)" - - echo $'\n\n================================================\n\n' - echo "Aziot-identity-service" - test-released-metadata "aziot-identity-service" "$(GitHubParameters.iisVersion)" - - test-released-artifact \ - "aziot-identity-service" \ - "$(GitHubParameters.iisVersion)" \ - "$(System.ArtifactsDirectory)" \ - "$(GitHubParameters.iisVersion)-1_$(os)_$(arch)_github.$(ext)" \ - "$(edgelet.maxPercentAllowed)" \ - "$(IsCheckPreviousPkg)" - displayName: Released Artifacts Smoke Tests - - - bash: | - # Source the scripts & Update version files - source $(Build.SourcesDirectory)/iotedge/scripts/linux/smokeTestHelper.sh - - test-released-images "$(Build.SourceBranchName)" - displayName: Released Images Smoke Tests - env: - DEVOPS_PAT: "$(IotEdge1-PAT-msazure)" + elif [[ "$(os)" == "debian11" ]]; then + setup-config-apt "https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb" + else + echo "Unsupported OS: $(os)" + exit 1; + fi + + echo $'\n\n================================================\n\n' + echo "Aziot-edge" + test-released-metadata "aziot-edge" "$(GitHubParameters.edgeletVersion)" + + test-released-artifact \ + "aziot-edge" \ + "$(GitHubParameters.edgeletVersion)" \ + "$(System.ArtifactsDirectory)" \ + "$(GitHubParameters.edgeletVersion)-1_$(os)_$(arch)_github.$(ext)" \ + "$(edgelet.maxPercentAllowed)" \ + "$(IsCheckPreviousPkg)" + + echo $'\n\n================================================\n\n' + echo "Aziot-identity-service" + test-released-metadata "aziot-identity-service" "$(GitHubParameters.iisVersion)" + + test-released-artifact \ + "aziot-identity-service" \ + "$(GitHubParameters.iisVersion)" \ + "$(System.ArtifactsDirectory)" \ + "$(GitHubParameters.iisVersion)-1_$(os)_$(arch)_github.$(ext)" \ + "$(edgelet.maxPercentAllowed)" \ + "$(IsCheckPreviousPkg)" + displayName: Released Artifacts Smoke Tests + + - bash: | + # Source the scripts & Update version files + source $(Build.SourcesDirectory)/iotedge/scripts/linux/smokeTestHelper.sh + + test-released-images "$(Build.SourceBranchName)" + displayName: Released Images Smoke Tests + env: + DEVOPS_PAT: "$(IotEdge1-PAT-msazure)" diff --git a/builds/misc/templates/build-packages.yaml b/builds/misc/templates/build-packages.yaml index 0097947eae5..af77d3cf097 100644 --- a/builds/misc/templates/build-packages.yaml +++ b/builds/misc/templates/build-packages.yaml @@ -5,9 +5,9 @@ parameters: stages: - ################################################################################ +################################################################################ - stage: CheckBuildPackages - ################################################################################ +################################################################################ displayName: Check For Source Code Changes pool: name: $(pool.linux.name) @@ -31,19 +31,17 @@ stages: - stage: BuildPackages ################################################################################ displayName: Build Packages + dependsOn: CheckBuildPackages condition: | or ( eq(${{ parameters['E2EBuild'] }}, false), eq(dependencies.CheckBuildPackages.outputs['check_source_change_edgelet.check_files.EDGELETCHANGES'], 'true') ) - pool: - name: $(pool.linux.name) - demands: - - ImageOverride -equals agent-aziotedge-ubuntu-20.04-docker - dependsOn: CheckBuildPackages jobs: + ################################################################################ - job: linux + ################################################################################ displayName: Linux pool: name: $(pool.linux.name) @@ -144,9 +142,48 @@ stages: ArtifactName: 'iotedged-$(os)-$(arch)' condition: and(succeededOrFailed(), or(eq(${{ parameters['E2EBuild'] }}, false), eq(variables.arch,'amd64'))) - ################################################################################ + ################################################################################ + - job: snap + ################################################################################ + displayName: Snap + strategy: + matrix: + amd64: + arch: amd64 + pool: $(pool.linux.name) + image: agent-aziotedge-ubuntu-22.04 + aarch64: + arch: aarch64 + pool: $(pool.linux.arm.name) + image: agent-aziotedge-ubuntu-22.04-arm64 + pool: + name: $(pool) + demands: + - ImageOverride -equals $(image) + steps: + - script: | + sudo snap install snapcraft --classic + lxd init --minimal + snapcraft --use-lxd + displayName: Build snap + condition: eq(${{ parameters['E2EBuild'] }}, false) + - task: CopyFiles@2 + displayName: Stage artifacts for publishing + condition: eq(${{ parameters['E2EBuild'] }}, false) + inputs: + Contents: '*.snap' + SourceFolder: '$(build.sourcesdirectory)' + TargetFolder: '$(build.artifactstagingdirectory)' + - task: PublishBuildArtifacts@1 + displayName: Publish artifacts + condition: eq(${{ parameters['E2EBuild'] }}, false) + inputs: + ArtifactName: 'iotedged-snap-$(arch)' + PathtoPublish: '$(build.artifactstagingdirectory)' + + ################################################################################ - job: mariner_linux - ################################################################################ + ################################################################################ displayName: Mariner_Linux condition: or(eq(variables['build.linux.mariner'], ''), eq(variables['build.linux.mariner'], true)) pool: @@ -228,9 +265,9 @@ stages: ArtifactName: 'iotedged-$(os)$(os_version)-$(arch)' condition: succeededOrFailed() - ################################################################################ + ################################################################################ - job: mariner_linux_arm64 - ################################################################################ + ################################################################################ displayName: Mariner_Linux on ARM64 condition: or(eq(variables['build.linux.mariner'], ''), eq(variables['build.linux.mariner'], true)) timeoutInMinutes: 90 diff --git a/edgelet/.cargo/config.toml b/edgelet/.cargo/config.toml new file mode 100644 index 00000000000..fed904abe07 --- /dev/null +++ b/edgelet/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +SOCKET_DIR = "/run/aziot" diff --git a/edgelet/Cargo.lock b/edgelet/Cargo.lock index 78b62fe3eb0..9e0bfb2326e 100644 --- a/edgelet/Cargo.lock +++ b/edgelet/Cargo.lock @@ -78,7 +78,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "aziot-cert-client-async" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-cert-common-http", "aziot-key-common", @@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "aziot-cert-common-http" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-key-common", "serde", @@ -100,7 +100,7 @@ dependencies = [ [[package]] name = "aziot-certd-config" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "cert-renewal", "hex", @@ -143,7 +143,7 @@ dependencies = [ [[package]] name = "aziot-identity-client-async" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-cert-common-http", "aziot-identity-common", @@ -157,7 +157,7 @@ dependencies = [ [[package]] name = "aziot-identity-common" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-key-common", "http-common", @@ -168,7 +168,7 @@ dependencies = [ [[package]] name = "aziot-identity-common-http" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-cert-common-http", "aziot-identity-common", @@ -181,7 +181,7 @@ dependencies = [ [[package]] name = "aziot-identityd-config" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-identity-common", "cert-renewal", @@ -196,7 +196,7 @@ dependencies = [ [[package]] name = "aziot-key-client" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-key-common", "aziot-key-common-http", @@ -211,7 +211,7 @@ dependencies = [ [[package]] name = "aziot-key-client-async" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-key-common", "aziot-key-common-http", @@ -224,7 +224,7 @@ dependencies = [ [[package]] name = "aziot-key-common" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "serde", ] @@ -232,7 +232,7 @@ dependencies = [ [[package]] name = "aziot-key-common-http" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-key-common", "http-common", @@ -242,7 +242,7 @@ dependencies = [ [[package]] name = "aziot-key-openssl-engine" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-key-client", "aziot-key-common", @@ -260,7 +260,7 @@ dependencies = [ [[package]] name = "aziot-keyd-config" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "http-common", "libc", @@ -270,7 +270,7 @@ dependencies = [ [[package]] name = "aziot-keys-common" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "pkcs11", "serde", @@ -280,7 +280,7 @@ dependencies = [ [[package]] name = "aziot-tpmd-config" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "http-common", "serde", @@ -289,7 +289,7 @@ dependencies = [ [[package]] name = "aziotctl-common" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "anyhow", "aziot-certd-config", @@ -372,7 +372,7 @@ checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cert-renewal" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "async-trait", "chrono", @@ -451,7 +451,7 @@ dependencies = [ [[package]] name = "config-common" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "serde", "toml", @@ -1121,7 +1121,7 @@ dependencies = [ [[package]] name = "http-common" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "async-trait", "base64 0.21.2", @@ -1433,7 +1433,7 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "logger" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "env_logger", "log", @@ -1571,7 +1571,7 @@ dependencies = [ [[package]] name = "openssl-build" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "cc", ] @@ -1613,7 +1613,7 @@ dependencies = [ [[package]] name = "openssl-sys2" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "openssl-build", "openssl-sys", @@ -1622,7 +1622,7 @@ dependencies = [ [[package]] name = "openssl2" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "foreign-types", "foreign-types-shared", @@ -1682,7 +1682,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs11" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "foreign-types-shared", "lazy_static", @@ -1699,7 +1699,7 @@ dependencies = [ [[package]] name = "pkcs11-sys" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" [[package]] name = "pkg-config" @@ -2121,7 +2121,7 @@ dependencies = [ [[package]] name = "test-common" version = "0.1.0" -source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#91e058880c4eb27756a15b74b52a031e0604207a" +source = "git+https://github.com/Azure/iot-identity-service?branch=release/1.4#9743701f21e729984200968e909cdd639b1f0799" dependencies = [ "aziot-identity-common", "aziot-identity-common-http", diff --git a/edgelet/Makefile b/edgelet/Makefile index 5ab8d8745a4..4e240292bdd 100644 --- a/edgelet/Makefile +++ b/edgelet/Makefile @@ -8,6 +8,9 @@ REVISION?=1 DEB_VERSION?=$(VERSION) DEB_REVISION?=$(REVISION) +# Default socket directory. Override by specifying on the CLI for make. +SOCKET_DIR ?= /run/aziot + # Converts debian versioning to rpm version # deb 1.0.1~dev100 ~> rpm 1.0.1-0.1.dev100 RPM_VERSION?=$(word 1,$(subst ~, , $(VERSION))) @@ -42,7 +45,7 @@ CARGOFLAGS=--manifest-path=$(srcdir)/Cargo.toml DPKGFLAGS=-b -rfakeroot -us -uc -i RPMBUILDFLAGS=-v -bb --clean -CARGO=cargo +CARGO=SOCKET_DIR="$(SOCKET_DIR)" cargo GIT=git GIT_ARCHIVEFLAGS=--prefix=$(PACKAGE)/ -o $(TARGET)/$(PACKAGE).tar.gz $(GIT_TAG) GIT_TAG=HEAD @@ -59,6 +62,14 @@ CONNECT_WORKLOAD_URI= LISTEN_MANAGEMENT_URI= LISTEN_WORKLOAD_URI= +# Enable special features for specific runtime platforms +# '' => none, 'snapd' => snapd features +PLATFORM_FEATURES ?= + +ifeq ($(PLATFORM_FEATURES), snapd) + CARGOFLAGS += --features snapctl +endif + all: VERSION=${VERSION} $(CARGO) build $(CARGOFLAGS) diff --git a/edgelet/build/linux/package-mariner.sh b/edgelet/build/linux/package-mariner.sh index 3c45f7e8383..0e54108ebdd 100755 --- a/edgelet/build/linux/package-mariner.sh +++ b/edgelet/build/linux/package-mariner.sh @@ -102,7 +102,7 @@ cargo vendor vendor # Configure Cargo to use vendored the deps -mkdir .cargo +mkdir -p .cargo cat > .cargo/config << EOF [source.crates-io] replace-with = "vendored-sources" diff --git a/edgelet/contrib/snap/command-chain/drop-privileges.sh b/edgelet/contrib/snap/command-chain/drop-privileges.sh new file mode 100755 index 00000000000..24953b38f0a --- /dev/null +++ b/edgelet/contrib/snap/command-chain/drop-privileges.sh @@ -0,0 +1,2 @@ +#!/bin/sh +$SNAP/usr/bin/setpriv --clear-groups --reuid snap_aziotedge --regid snap_aziotedge -- "$@" diff --git a/edgelet/contrib/snap/command-chain/handle-exit-status-153.sh b/edgelet/contrib/snap/command-chain/handle-exit-status-153.sh new file mode 100755 index 00000000000..1bf3b45c322 --- /dev/null +++ b/edgelet/contrib/snap/command-chain/handle-exit-status-153.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -eu +EXIT_STATUS=0 + +"$@" || EXIT_STATUS=$? + +if [ $EXIT_STATUS -eq 153 ] ; then + snapctl stop $SNAP_INSTANCE_NAME.aziot-edged +fi + +exit $EXIT_STATUS diff --git a/edgelet/contrib/snap/command-chain/make-socket-directory.sh b/edgelet/contrib/snap/command-chain/make-socket-directory.sh new file mode 100755 index 00000000000..7159a0fa2e6 --- /dev/null +++ b/edgelet/contrib/snap/command-chain/make-socket-directory.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +echo "Making /var/run/iotedge if it does not exist" +mkdir -p /var/run/iotedge +echo "Successfully made /var/run/iotedge if it did not exist" +chown snap_aziotedge:snap_aziotedge /var/run/iotedge + +exec "$@" diff --git a/edgelet/contrib/snap/socat.sh b/edgelet/contrib/snap/socat.sh new file mode 100755 index 00000000000..2c769e77c82 --- /dev/null +++ b/edgelet/contrib/snap/socat.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +$SNAP/usr/bin/socat UNIX-LISTEN:$SNAP_COMMON/docker-proxy.sock,reuseaddr,fork,user=snap_aziotedge,group=snap_aziotedge UNIX-CONNECT:/var/run/docker.sock diff --git a/edgelet/edgelet-settings/src/base/aziot.rs b/edgelet/edgelet-settings/src/base/aziot.rs index 55049418754..720c76f294d 100644 --- a/edgelet/edgelet-settings/src/base/aziot.rs +++ b/edgelet/edgelet-settings/src/base/aziot.rs @@ -23,12 +23,21 @@ pub struct Endpoints { impl Default for Endpoints { fn default() -> Self { Endpoints { - aziot_certd_url: url::Url::parse("unix:///run/aziot/certd.sock") - .expect("cannot fail to parse hardcoded url"), - aziot_keyd_url: url::Url::parse("unix:///run/aziot/keyd.sock") - .expect("cannot fail to parse hardcoded url"), - aziot_identityd_url: url::Url::parse("unix:///run/aziot/identityd.sock") - .expect("cannot fail to parse hardcoded url"), + aziot_certd_url: url::Url::parse(&format!( + "unix://{}/certd.sock", + option_env!("SOCKET_DIR").unwrap_or("/run/aziot") + )) + .expect("cannot fail to parse hardcoded url"), + aziot_keyd_url: url::Url::parse(&format!( + "unix://{}/keyd.sock", + option_env!("SOCKET_DIR").unwrap_or("/run/aziot") + )) + .expect("cannot fail to parse hardcoded url"), + aziot_identityd_url: url::Url::parse(&format!( + "unix://{}/identityd.sock", + option_env!("SOCKET_DIR").unwrap_or("/run/aziot") + )) + .expect("cannot fail to parse hardcoded url"), } } } diff --git a/edgelet/iotedge/Cargo.toml b/edgelet/iotedge/Cargo.toml index 9bed1f8e237..d8b8ff673d0 100644 --- a/edgelet/iotedge/Cargo.toml +++ b/edgelet/iotedge/Cargo.toml @@ -54,3 +54,6 @@ edgelet-settings = { path = "../edgelet-settings", features = ["settings-docker" edgelet-utils = { path = "../edgelet-utils" } http-common = { git = "https://github.com/Azure/iot-identity-service", branch = "release/1.4" } support-bundle = { path = "../support-bundle" } + +[features] +snapctl = ["aziotctl-common/snapctl", "support-bundle/snapctl"] diff --git a/edgelet/iotedge/src/config/apply.rs b/edgelet/iotedge/src/config/apply.rs index 257cf087aa7..cd42c732913 100644 --- a/edgelet/iotedge/src/config/apply.rs +++ b/edgelet/iotedge/src/config/apply.rs @@ -17,7 +17,19 @@ const TRUST_BUNDLE_USER_ALIAS: &str = "trust-bundle-user"; const LABELS: &[&str] = &["net.azure-devices.edge.owner=Microsoft.Azure.Devices.Edge.Agent"]; +const USER_AZIOTKS: Option<&'static str> = option_env!("USER_AZIOTKS"); +const USER_AZIOTCS: Option<&'static str> = option_env!("USER_AZIOTCS"); +const USER_AZIOTID: Option<&'static str> = option_env!("USER_AZIOTID"); +const USER_AZIOTTPM: Option<&'static str> = option_env!("USER_AZIOTTPM"); +const USER_IOTEDGE: Option<&'static str> = option_env!("USER_IOTEDGE"); + pub async fn execute(config: &Path) -> Result<(), std::borrow::Cow<'static, str>> { + // unwrap_or is currently const: unstable so until that resolves itself do this at runtime + let aziotks_username: &'static str = USER_AZIOTKS.unwrap_or("aziotks"); + let aziotcs_username: &'static str = USER_AZIOTCS.unwrap_or("aziotcs"); + let aziotid_username: &'static str = USER_AZIOTID.unwrap_or("aziotid"); + let aziottpm_username: &'static str = USER_AZIOTTPM.unwrap_or("aziottpm"); + let iotedge_username: &'static str = USER_IOTEDGE.unwrap_or("iotedge"); // In production, running as root is the easiest way to guarantee the tool has write access to every service's config file. // But it's convenient to not do this for the sake of development because the the development machine doesn't necessarily // have the package installed and the users created, and it's easier to have the config files owned by the current user anyway. @@ -27,25 +39,60 @@ pub async fn execute(config: &Path) -> Result<(), std::borrow::Cow<'static, str> // Otherwise, tell the user to re-run as root. let (aziotks_user, aziotcs_user, aziotid_user, aziottpm_user, iotedge_user) = if nix::unistd::Uid::current().is_root() { - let aziotks_user = nix::unistd::User::from_name("aziotks") - .map_err(|err| format!("could not query aziotks user information: {}", err))? - .ok_or("could not query aziotks user information")?; + let aziotks_user = nix::unistd::User::from_name(aziotks_username) + .map_err(|err| { + format!( + "could not query {aziotks_username} user information: {}", + err + ) + })? + .ok_or(format!( + "could not query {aziotks_username} user information" + ))?; - let aziotcs_user = nix::unistd::User::from_name("aziotcs") - .map_err(|err| format!("could not query aziotcs user information: {}", err))? - .ok_or("could not query aziotcs user information")?; + let aziotcs_user = nix::unistd::User::from_name(aziotcs_username) + .map_err(|err| { + format!( + "could not query {aziotcs_username} user information: {}", + err + ) + })? + .ok_or(format!( + "could not query {aziotcs_username} user information" + ))?; - let aziotid_user = nix::unistd::User::from_name("aziotid") - .map_err(|err| format!("could not query aziotid user information: {}", err))? - .ok_or("could not query aziotid user information")?; + let aziotid_user = nix::unistd::User::from_name(aziotid_username) + .map_err(|err| { + format!( + "could not query {aziotid_username} user information: {}", + err + ) + })? + .ok_or(format!( + "could not query {aziotid_username} user information" + ))?; - let aziottpm_user = nix::unistd::User::from_name("aziottpm") - .map_err(|err| format!("could not query aziottpm user information: {}", err))? - .ok_or("could not query aziottpm user information")?; + let aziottpm_user = nix::unistd::User::from_name(aziottpm_username) + .map_err(|err| { + format!( + "could not query {aziottpm_username} user information: {}", + err + ) + })? + .ok_or(format!( + "could not query {aziottpm_username} user information" + ))?; - let iotedge_user = nix::unistd::User::from_name("iotedge") - .map_err(|err| format!("could not query iotedge user information: {}", err))? - .ok_or("could not query iotedge user information")?; + let iotedge_user = nix::unistd::User::from_name(iotedge_username) + .map_err(|err| { + format!( + "could not query {iotedge_username} user information: {}", + err + ) + })? + .ok_or(format!( + "could not query {iotedge_username} user information" + ))?; ( aziotks_user, diff --git a/edgelet/iotedge/src/system.rs b/edgelet/iotedge/src/system.rs index fa8e9d7a09e..29badaea0c4 100644 --- a/edgelet/iotedge/src/system.rs +++ b/edgelet/iotedge/src/system.rs @@ -6,14 +6,36 @@ use lazy_static::lazy_static; use aziotctl_common::system::{ get_status, get_system_logs as logs, restart, set_log_level as log_level, stop, - ServiceDefinition, SERVICE_DEFINITIONS as IS_SERVICES, + ServiceDefinition, }; +#[cfg(not(feature = "snapctl"))] +use aziotctl_common::system::SERVICE_DEFINITIONS as IS_SERVICES; + use aziot_identity_client_async::Client as IdentityClient; use aziot_identity_common_http::ApiVersion; use crate::error::Error; +#[cfg(feature = "snapctl")] +lazy_static! { + static ref IOTEDGED: ServiceDefinition = { + ServiceDefinition { + service: "snap.azure-iot-edge.aziot-edged.service", + sockets: &[], + } + }; + static ref DOCKERPROXY: ServiceDefinition = { + ServiceDefinition { + service: "snap.azure-iot-edge.docker-proxy.service", + sockets: &[], + } + }; + static ref SERVICE_DEFINITIONS: Vec<&'static ServiceDefinition> = + [&*DOCKERPROXY, &*IOTEDGED].into_iter().collect(); +} + +#[cfg(not(feature = "snapctl"))] lazy_static! { static ref IOTEDGED: ServiceDefinition = { // If IOTEDGE_LISTEN_MANAGEMENT_URI isn't set at compile-time, assume socket activation is being used. @@ -83,8 +105,11 @@ impl System { } pub async fn reprovision() -> Result<(), Error> { - let uri = url::Url::parse("unix:///run/aziot/identityd.sock") - .expect("hard-coded URI should parse"); + let uri = url::Url::parse(&format!( + "unix://{}/identityd.sock", + option_env!("SOCKET_DIR").unwrap_or("/run/aziot") + )) + .expect("hard-coded URI should parse"); let client = IdentityClient::new( ApiVersion::V2020_09_01, http_common::Connector::new(&uri).map_err(|err| { diff --git a/edgelet/support-bundle/Cargo.toml b/edgelet/support-bundle/Cargo.toml index 7a5978ed653..39fb60f9af8 100644 --- a/edgelet/support-bundle/Cargo.toml +++ b/edgelet/support-bundle/Cargo.toml @@ -15,3 +15,6 @@ zip = { version = "0.6", features = ["deflate"], default-features = false } edgelet-core = { path = "../edgelet-core" } edgelet-settings = {path = "../edgelet-settings" } + +[features] +snapctl = [] diff --git a/edgelet/support-bundle/src/support_bundle.rs b/edgelet/support-bundle/src/support_bundle.rs index ada79d0accf..016bc3368b9 100644 --- a/edgelet/support-bundle/src/support_bundle.rs +++ b/edgelet/support-bundle/src/support_bundle.rs @@ -16,6 +16,7 @@ use crate::shell_util::{ get_docker_networks, write_check, write_inspect, write_network_inspect, write_system_log, }; +#[cfg(not(feature = "snapctl"))] const SYSTEM_MODULES: &[(&str, &str)] = &[ ("aziot-keyd", "aziot-keyd"), ("aziot-certd", "aziot-certd"), @@ -24,6 +25,16 @@ const SYSTEM_MODULES: &[(&str, &str)] = &[ ("docker", "docker"), ]; +#[cfg(feature = "snapctl")] +const SYSTEM_MODULES: &[(&str, &str)] = &[ + ("aziot-keyd", "snap.azure-iot-identity.keyd"), + ("aziot-certd", "snap.azure-iot-identity.certd"), + ("aziot-identityd", "snap.azure-iot-identity.identityd"), + ("aziot-edged", "snap.azure-iot-edge.edged"), + ("docker-proxy", "snap.azure-iot-edge.docker-proxy"), + ("docker", "snap.docker.dockerd"), +]; + /// # Errors /// /// Will return `Err` if unable to collect support bundle diff --git a/snap/hooks/configure b/snap/hooks/configure new file mode 100755 index 00000000000..132508b9099 --- /dev/null +++ b/snap/hooks/configure @@ -0,0 +1,53 @@ +#!/bin/bash + +set -ux +exec 1> >(logger -s -t "$SNAP_INSTANCE_NAME.$(basename $0)") 2>&1 + +toml_kvp() { + printf "%s = \"%s\"\n" "$1" "$2" +} + +toml_new_section() { + printf "\n\n" + printf "[%s]\n" "$1" +} + +# make sure we are plugged in to the identity service +if ! snapctl is-connected identity-service; then + exit 0 +fi + +mkdir -p /etc/aziot/edged/config.d + +snapctl get raw-config > /etc/aziot/config.toml + +{ + toml_kvp "hostname" "$(hostnamectl hostname)" +} | tee /etc/aziot/keyd/config.d/01-snap.toml /etc/aziot/certd/config.d/01-snap.toml /etc/aziot/identityd/config.d/01-snap.toml /etc/aziot/tpmd/config.d/01-snap.toml + +{ + toml_kvp "hostname" "$(hostnamectl hostname)" + + toml_new_section "agent" + toml_kvp "name" "edgeAgent" + toml_kvp "type" "docker" + + toml_new_section "agent.env" + toml_kvp "EDGEAGENTUSER_ID" "0" + + toml_new_section "connect" + toml_kvp "workload_uri" "unix:///var/run/iotedge/workload.sock" + toml_kvp "management_uri" "unix:///var/run/iotedge/mgmt.sock" + + toml_new_section "listen" + toml_kvp "workload_uri" "unix:///var/run/iotedge/workload.sock" + toml_kvp "management_uri" "unix:///var/run/iotedge/mgmt.sock" + + toml_new_section "moby_runtime" + toml_kvp "uri" "unix://$SNAP_COMMON/docker-proxy.sock" + toml_kvp "network" "azure-iot-edge" +} > /etc/aziot/edged/config.d/01-snap.toml + +$SNAP/usr/bin/iotedge config apply + +snapctl restart $SNAP_INSTANCE_NAME.aziot-edged diff --git a/snap/hooks/install b/snap/hooks/install new file mode 100755 index 00000000000..6f22d73a257 --- /dev/null +++ b/snap/hooks/install @@ -0,0 +1,8 @@ +#!/bin/bash + +set -eux +exec 1> >(logger -s -t "$SNAP_INSTANCE_NAME.$(basename $0)") 2>&1 + +mkdir -p $SNAP_COMMON/var/{lib/{aziot/edged,iotedge},log} + +chown -R snap_aziotedge:snap_aziotedge $SNAP_COMMON/var/{lib/{aziot/edged,iotedge},log} diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 00000000000..391fa227f0c --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,178 @@ +name: azure-iot-edge +base: core22 # the base snap is the execution environment for this snap +summary: Managed solution for deploying and configuring software on IoT devices +description: | + Azure IoT Edge is a fully managed service that delivers cloud intelligence + locally by deploying and running artificial intelligence (AI), Azure services, + and custom logic directly on cross-platform IoT devices. Run your IoT solution + securely and at scale—whether in the cloud or offline. + +confinement: strict # use 'strict' once you have the right plugs and slots +adopt-info: edgelet + +system-usernames: + snap_aziotedge: shared + +package-repositories: + - type: apt + components: [ main ] + suites: [ jammy ] + url: https://packages.microsoft.com/ubuntu/22.04/prod + key-id: BC528686B50D79E339D3721CEB3E94ADBE1229CF + +parts: + rust-toolchain: + plugin: nil + build-packages: + - curl + build-environment: + - PATH: "$PATH:$HOME/.cargo/bin" + override-build: | + mkdir -p $HOME/.cargo/bin + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path --profile minimal -y + cd $CRAFT_PROJECT_DIR/edgelet + rustup update + edgelet: + after: [ rust-toolchain ] + source: edgelet/ + plugin: make + make-parameters: + - "CONNECT_MANAGEMENT_URI=unix:///var/run/iotedge/mgmt.sock" + - "CONNECT_WORKLOAD_URI=unix:///var/run/iotedge/workload.sock" + - "LISTEN_MANAGEMENT_URI=unix:///var/run/iotedge/mgmt.sock" + - "LISTEN_WORKLOAD_URI=unix:///var/run/iotedge/workload.sock" + - "PLATFORM_FEATURES=snapd" + build-environment: + - PATH: "$PATH:$HOME/.cargo/bin" + - SOCKET_DIR: "/var/sockets/aziot" + - USER_AZIOTKS: "root" + - USER_AZIOTCS: "root" + - USER_AZIOTID: "root" + - USER_AZIOTTPM: "root" + - USER_IOTEDGE: "snap_aziotedge" + override-build: | + rm -rf $CRAFT_PART_BUILD/target + SC_VERSION="$(cat $CRAFT_PART_SRC/version.txt)" + craftctl set version="$SC_VERSION" + # if version contains substing "dev" set grade to devel, else stable + if test "${SC_VERSION#*dev}" != "$SC_VERSION" ; then + craftctl set grade=devel + else + craftctl set grade=stable + fi + make install \ + PLATFORM_FEATURES=snapd \ + CONNECT_MANAGEMENT_URI=unix:///var/run/iotedge/mgmt.sock \ + CONNECT_WORKLOAD_URI=unix:///var/run/iotedge/workload.sock \ + LISTEN_MANAGEMENT_URI=unix:///var/run/iotedge/mgmt.sock \ + LISTEN_WORKLOAD_URI=unix:///var/run/iotedge/workload.sock \ + DESTDIR=$CRAFT_PART_INSTALL + build-packages: + - binutils + - build-essential + - ca-certificates + - curl + - cmake + - debhelper + - file + - git + - make + - gcc + - g++ + - pkg-config + - libcurl4-openssl-dev + - libssl-dev + - uuid-dev + command-chain: + plugin: dump + source: edgelet/contrib/ + stage-packages: [ util-linux ] + stage: + - snap/command-chain + - usr/bin/setpriv + - usr/share/doc/util-linux/copyright + socat: + plugin: dump + source: edgelet/contrib/snap + stage-packages: [ socat ] + organize: + socat.sh: bin/socat.sh + docker-cli: + plugin: nil + stage-packages: + - moby-cli + +apps: + aziot-edged: + command-chain: + - snap/command-chain/handle-exit-status-153.sh + - snap/command-chain/make-socket-directory.sh + - snap/command-chain/drop-privileges.sh + command: usr/libexec/aziot/aziot-edged + daemon: simple + plugs: + - docker + - identity-service + - mount-observe + - network + - network-bind + - system-observe + - run-iotedge + iotedge: + command: usr/bin/iotedge + plugs: + - docker + - identity-service + - home + - log-observe + - mount-observe + - network + - system-observe + - run-iotedge + docker-proxy: + command: bin/socat.sh + daemon: simple + plugs: + - docker + - network + - network-bind + +hooks: + configure: + plugs: + - aziotctl-executables + - docker + - hostname-control + - identity-service + - log-observe + - mount-observe + +environment: + PATH: "$PATH:$SNAP/aziotctl/bin:$SNAP/usr/bin" + +plugs: + aziotctl-executables: + interface: content + content: aziotctl-executables + target: $SNAP/aziotctl + identity-service: + interface: content + content: aziot-identity-service + target: $SNAP_COMMON + run-iotedge: + interface: system-files + write: [ /var/run/iotedge, /run/iotedge ] + +layout: + /var/lib/aziot: + symlink: $SNAP_COMMON/var/lib/aziot + /var/lib/iotedge: + symlink: $SNAP_COMMON/var/lib/iotedge + /var/sockets/aziot: + symlink: $SNAP_COMMON/shared/sockets/aziot + /var/secrets/aziot: + symlink: $SNAP_COMMON/shared/secrets/aziot + /etc/aziot: + symlink: $SNAP_COMMON/shared/config/aziot + /usr/libexec/aziot/aziot-edged: + symlink: $SNAP/usr/libexec/aziot/aziot-edged diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/DaemonConfiguration.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/DaemonConfiguration.cs index 1370f8d253a..36732322cd9 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/DaemonConfiguration.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/DaemonConfiguration.cs @@ -13,43 +13,40 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common public class DaemonConfiguration { - struct Config - { - public string ConfigPath; - public TomlDocument Document; - } - const string GlobalEndPoint = "https://global.azure-devices-provisioning.net"; - Config config; + TomlDocument document; + string path; public DaemonConfiguration(string superTomlPath) { Directory.CreateDirectory(Directory.GetParent(superTomlPath).FullName); string contents = File.Exists(superTomlPath) ? File.ReadAllText(superTomlPath) : string.Empty; - this.config = new Config - { - ConfigPath = superTomlPath, - Document = new TomlDocument(contents) - }; + this.document = new TomlDocument(contents); + this.path = superTomlPath; } public void AddHttpsProxy(Uri proxy) { - this.config.Document.ReplaceOrAdd("agent.env.https_proxy", proxy.ToString()); + this.document.ReplaceOrAdd("agent.env.https_proxy", proxy.ToString()); // The config file is configured during test suite initialization, before we know which // protocol a given test will use. Always use AmqpWs, and when each test deploys a // configuration, it can update the config to use whatever it wants. - this.config.Document.ReplaceOrAdd("agent.env.UpstreamProtocol", "AmqpWs"); + this.document.ReplaceOrAdd("agent.env.UpstreamProtocol", "AmqpWs"); + } + + public void AddAgentUserId(string uid) + { + this.document.ReplaceOrAdd("agent.env.EDGEAGENTUSER_ID", uid); } void SetBasicDpsParam(string idScope) { - this.config.Document.ReplaceOrAdd("auto_reprovisioning_mode", "AlwaysOnStartup"); + this.document.ReplaceOrAdd("auto_reprovisioning_mode", "AlwaysOnStartup"); - this.config.Document.RemoveIfExists("provisioning"); - this.config.Document.ReplaceOrAdd("provisioning.source", "dps"); - this.config.Document.ReplaceOrAdd("provisioning.global_endpoint", GlobalEndPoint); - this.config.Document.ReplaceOrAdd("provisioning.id_scope", idScope); + this.document.RemoveIfExists("provisioning"); + this.document.ReplaceOrAdd("provisioning.source", "dps"); + this.document.ReplaceOrAdd("provisioning.global_endpoint", GlobalEndPoint); + this.document.ReplaceOrAdd("provisioning.id_scope", idScope); } public void SetManualSasProvisioning( @@ -58,24 +55,24 @@ public void SetManualSasProvisioning( string deviceId, string key) { - this.config.Document.ReplaceOrAdd("auto_reprovisioning_mode", "AlwaysOnStartup"); + this.document.ReplaceOrAdd("auto_reprovisioning_mode", "AlwaysOnStartup"); parentHostname.ForEach(parent_hostame => this.SetParentHostname(parent_hostame)); - this.config.Document.RemoveIfExists("provisioning"); - this.config.Document.ReplaceOrAdd("provisioning.source", "manual"); - this.config.Document.ReplaceOrAdd("provisioning.iothub_hostname", hubHostname); - this.config.Document.ReplaceOrAdd("provisioning.device_id", deviceId); - this.config.Document.ReplaceOrAdd("provisioning.authentication.method", "sas"); - this.config.Document.ReplaceOrAdd("provisioning.authentication.device_id_pk.value", key); + this.document.RemoveIfExists("provisioning"); + this.document.ReplaceOrAdd("provisioning.source", "manual"); + this.document.ReplaceOrAdd("provisioning.iothub_hostname", hubHostname); + this.document.ReplaceOrAdd("provisioning.device_id", deviceId); + this.document.ReplaceOrAdd("provisioning.authentication.method", "sas"); + this.document.ReplaceOrAdd("provisioning.authentication.device_id_pk.value", key); } public void SetImageGarbageCollection(int minutesUntilCleanup) { - this.config.Document.ReplaceOrAdd("image_garbage_collection.enabled", true); - this.config.Document.ReplaceOrAdd("image_garbage_collection.cleanup_recurrence", "1d"); - this.config.Document.ReplaceOrAdd("image_garbage_collection.image_age_cleanup_threshold", "10s"); + this.document.ReplaceOrAdd("image_garbage_collection.enabled", true); + this.document.ReplaceOrAdd("image_garbage_collection.cleanup_recurrence", "1d"); + this.document.ReplaceOrAdd("image_garbage_collection.image_age_cleanup_threshold", "10s"); string cleanupTime = DateTime.Now.Add(new TimeSpan(0, 0, minutesUntilCleanup, 0)).ToString("HH:mm"); - this.config.Document.ReplaceOrAdd("image_garbage_collection.cleanup_time", cleanupTime); + this.document.ReplaceOrAdd("image_garbage_collection.cleanup_time", cleanupTime); } public void SetDeviceManualX509( @@ -95,24 +92,24 @@ public void SetDeviceManualX509( throw new InvalidOperationException($"{identityPkPath} does not exist"); } - this.config.Document.ReplaceOrAdd("auto_reprovisioning_mode", "AlwaysOnStartup"); + this.document.ReplaceOrAdd("auto_reprovisioning_mode", "AlwaysOnStartup"); parentHostname.ForEach(parent_hostame => this.SetParentHostname(parent_hostame)); - this.config.Document.RemoveIfExists("provisioning"); - this.config.Document.ReplaceOrAdd("provisioning.source", "manual"); - this.config.Document.ReplaceOrAdd("provisioning.iothub_hostname", hubhostname); - this.config.Document.ReplaceOrAdd("provisioning.device_id", deviceId); - this.config.Document.ReplaceOrAdd("provisioning.authentication.method", "x509"); - this.config.Document.ReplaceOrAdd("provisioning.authentication.identity_cert", "file://" + identityCertPath); - this.config.Document.ReplaceOrAdd("provisioning.authentication.identity_pk", "file://" + identityPkPath); + this.document.RemoveIfExists("provisioning"); + this.document.ReplaceOrAdd("provisioning.source", "manual"); + this.document.ReplaceOrAdd("provisioning.iothub_hostname", hubhostname); + this.document.ReplaceOrAdd("provisioning.device_id", deviceId); + this.document.ReplaceOrAdd("provisioning.authentication.method", "x509"); + this.document.ReplaceOrAdd("provisioning.authentication.identity_cert", "file://" + identityCertPath); + this.document.ReplaceOrAdd("provisioning.authentication.identity_pk", "file://" + identityPkPath); } public void SetDpsSymmetricKey(string idScope, string registrationId, string deviceKey) { this.SetBasicDpsParam(idScope); - this.config.Document.ReplaceOrAdd("provisioning.attestation.method", "symmetric_key"); - this.config.Document.ReplaceOrAdd("provisioning.attestation.registration_id", registrationId); - this.config.Document.ReplaceOrAdd("provisioning.attestation.symmetric_key.value", deviceKey); + this.document.ReplaceOrAdd("provisioning.attestation.method", "symmetric_key"); + this.document.ReplaceOrAdd("provisioning.attestation.registration_id", registrationId); + this.document.ReplaceOrAdd("provisioning.attestation.symmetric_key.value", deviceKey); } public void SetDpsX509(string idScope, string identityCertPath, string identityPkPath) @@ -128,16 +125,16 @@ public void SetDpsX509(string idScope, string identityCertPath, string identityP } this.SetBasicDpsParam(idScope); - this.config.Document.ReplaceOrAdd("provisioning.attestation.method", "x509"); - this.config.Document.ReplaceOrAdd("provisioning.attestation.identity_cert", "file://" + identityCertPath); - this.config.Document.ReplaceOrAdd("provisioning.attestation.identity_pk", "file://" + identityPkPath); + this.document.ReplaceOrAdd("provisioning.attestation.method", "x509"); + this.document.ReplaceOrAdd("provisioning.attestation.identity_cert", "file://" + identityCertPath); + this.document.ReplaceOrAdd("provisioning.attestation.identity_pk", "file://" + identityPkPath); } public void SetEdgeAgentImage(string value, IEnumerable registries) { - this.config.Document.ReplaceOrAdd("agent.name", "edgeAgent"); - this.config.Document.ReplaceOrAdd("agent.type", "docker"); - this.config.Document.ReplaceOrAdd("agent.config.image", value); + this.document.ReplaceOrAdd("agent.name", "edgeAgent"); + this.document.ReplaceOrAdd("agent.type", "docker"); + this.document.ReplaceOrAdd("agent.config.image", value); // Currently, the only place for registries is [agent.config.auth] // So only one registry is supported. @@ -148,60 +145,55 @@ public void SetEdgeAgentImage(string value, IEnumerable registries) foreach (Registry registry in registries) { - this.config.Document.ReplaceOrAdd("agent.config.auth.serveraddress", registry.Address); - this.config.Document.ReplaceOrAdd("agent.config.auth.username", registry.Username); - this.config.Document.ReplaceOrAdd("agent.config.auth.password", registry.Password); + this.document.ReplaceOrAdd("agent.config.auth.serveraddress", registry.Address); + this.document.ReplaceOrAdd("agent.config.auth.username", registry.Username); + this.document.ReplaceOrAdd("agent.config.auth.password", registry.Password); } } public void SetDeviceHostname(string value) { - this.config.Document.ReplaceOrAdd("hostname", value); + this.document.ReplaceOrAdd("hostname", value); + } + + public void SetDeviceHomedir(string value) + { + this.document.ReplaceOrAdd("homedir", value); } public void SetParentHostname(string value) { - this.config.Document.ReplaceOrAdd("parent_hostname", value); + this.document.ReplaceOrAdd("parent_hostname", value); + } + + public void SetMobyRuntimeUri(string value) + { + this.document.ReplaceOrAdd("moby_runtime.uri", value); + this.document.ReplaceOrAdd("moby_runtime.network", "azure-iot-edge"); } public void SetConnectSockets(string workloadUri, string managementUri) { - this.config.Document.ReplaceOrAdd("connect.workload_uri", workloadUri); - this.config.Document.ReplaceOrAdd("connect.management_uri", managementUri); + this.document.ReplaceOrAdd("connect.workload_uri", workloadUri); + this.document.ReplaceOrAdd("connect.management_uri", managementUri); } public void SetListenSockets(string workloadUri, string managementUri) { - this.config.Document.ReplaceOrAdd("listen.workload_uri", workloadUri); - this.config.Document.ReplaceOrAdd("listen.management_uri", managementUri); + this.document.ReplaceOrAdd("listen.workload_uri", workloadUri); + this.document.ReplaceOrAdd("listen.management_uri", managementUri); } public void SetCertificates(CaCertificates certs) { - if (!File.Exists(certs.CertificatePath)) - { - throw new InvalidOperationException($"{certs.CertificatePath} does not exist"); - } - - if (!File.Exists(certs.KeyPath)) - { - throw new InvalidOperationException($"{certs.KeyPath} does not exist"); - } - - if (!File.Exists(certs.TrustedCertificatesPath)) - { - throw new InvalidOperationException($"{certs.TrustedCertificatesPath} does not exist"); - } - - this.config.Document.ReplaceOrAdd("edge_ca.cert", "file://" + certs.CertificatePath); - this.config.Document.ReplaceOrAdd("edge_ca.pk", "file://" + certs.KeyPath); - this.config.Document.ReplaceOrAdd("trust_bundle_cert", "file://" + certs.TrustedCertificatesPath); + this.document.ReplaceOrAdd("edge_ca.cert", "file://" + certs.CertificatePath); + this.document.ReplaceOrAdd("edge_ca.pk", "file://" + certs.KeyPath); + this.document.ReplaceOrAdd("trust_bundle_cert", "file://" + certs.TrustedCertificatesPath); } public async Task UpdateAsync(CancellationToken token) { - await File.WriteAllTextAsync(this.config.ConfigPath, this.config.Document.ToString()); - // Serilog.Log.Information(await File.ReadAllTextAsync(path)); + await File.WriteAllTextAsync(this.path, this.document.ToString()); } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeLogs.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeLogs.cs index f81ece0bd9d..4e92af93bc6 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeLogs.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeLogs.cs @@ -13,20 +13,20 @@ public static class EdgeLogs { // Make an effort to collect logs, but swallow any exceptions to prevent tests/fixtures // from failing if this function fails. - public static async Task> CollectAsync(DateTime testStartTime, string filePrefix, CancellationToken token) + public static async Task> CollectAsync(DateTime testStartTime, string filePrefix, IotedgeCli cli, CancellationToken token) { var paths = new List(); // Save module logs try { - string[] output = await Process.RunAsync("iotedge", "list", token); + string[] output = await cli.RunAsync("list", token); string[] modules = output.Select(ln => ln.Split(null as char[], StringSplitOptions.RemoveEmptyEntries).First()).Skip(1).ToArray(); foreach (string name in modules) { string moduleLog = $"{filePrefix}-{name}.log"; - output = await Process.RunAsync("iotedge", $"logs {name}", token, logVerbose: false); + output = await cli.RunAsync($"logs {name}", token, logCommand: true, logOutput: false); await File.WriteAllLinesAsync(moduleLog, output, token); paths.Add(moduleLog); } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeModule.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeModule.cs index a6b574132f8..f7073e300e5 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeModule.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeModule.cs @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common using Microsoft.Azure.Devices.Shared; using Newtonsoft.Json; using Newtonsoft.Json.Linq; - using Newtonsoft.Json.Serialization; using Serilog; public enum EdgeModuleStatus @@ -35,7 +34,7 @@ public EdgeModule(string id, string deviceId, IotHub iotHub) this.iotHub = iotHub; } - public static Task WaitForStatusAsync(IEnumerable modules, EdgeModuleStatus desired, CancellationToken token) + public static Task WaitForStatusAsync(IEnumerable modules, EdgeModuleStatus desired, IotedgeCli cli, CancellationToken token) { string[] moduleIds = modules.Select(m => m.Id.TrimStart('$')).Distinct().ToArray(); @@ -46,7 +45,7 @@ async Task WaitForStatusAsync() await Retry.Do( async () => { - string[] output = await Process.RunAsync("iotedge", "list", token); + string[] output = await cli.RunAsync("list", token); return output .Where( @@ -90,8 +89,8 @@ static bool DaemonNotReady(string details) => desired.ToString().ToLower()); } - public Task WaitForStatusAsync(EdgeModuleStatus desired, CancellationToken token) => - WaitForStatusAsync(new[] { this }, desired, token); + public Task WaitForStatusAsync(EdgeModuleStatus desired, IotedgeCli cli, CancellationToken token) => + WaitForStatusAsync(new[] { this }, desired, cli, token); public Task WaitForEventsReceivedAsync( DateTime seekTime, diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeRuntime.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeRuntime.cs index 9f9a02a7a52..e8da88f5e19 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeRuntime.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeRuntime.cs @@ -40,6 +40,7 @@ public EdgeRuntime(string deviceId, Option agentImage, Option hu // receive it and start up all the modules. public async Task DeployConfigurationAsync( Action addConfig, + IotedgeCli cli, CancellationToken token, bool nestedEdge) { @@ -78,12 +79,12 @@ public async Task DeployConfigurationAsync( EdgeModule[] modules = edgeConfiguration.ModuleNames .Select(id => new EdgeModule(id, this.DeviceId, this.iotHub)) .ToArray(); - await EdgeModule.WaitForStatusAsync(modules, EdgeModuleStatus.Running, token); + await EdgeModule.WaitForStatusAsync(modules, EdgeModuleStatus.Running, cli, token); await edgeConfiguration.VerifyAsync(this.iotHub, token); return new EdgeDeployment(deployTime, modules); } - public Task DeployConfigurationAsync(CancellationToken token, bool nestedEdge) => - this.DeployConfigurationAsync(_ => { }, token, nestedEdge); + public Task DeployConfigurationAsync(IotedgeCli cli, CancellationToken token, bool nestedEdge) => + this.DeployConfigurationAsync(_ => { }, cli, token, nestedEdge); } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/IEdgeDaemon.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/IEdgeDaemon.cs index 10679471a10..dcf4a2253cf 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/IEdgeDaemon.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/IEdgeDaemon.cs @@ -2,22 +2,18 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common { using System; - using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util; - public enum EdgeDaemonStatus - { - Running = ServiceControllerStatus.Running, - Stopped = ServiceControllerStatus.Stopped - } - public interface IEdgeDaemon { - Task InstallAsync(Option packagesPath, Option proxy, CancellationToken token); + Task InstallAsync(Option proxy, CancellationToken token); - Task ConfigureAsync(Func> config, CancellationToken token, bool restart = true); + Task ConfigureAsync( + Func> config, + CancellationToken token, + bool restart = true); Task StartAsync(CancellationToken token); @@ -25,6 +21,8 @@ public interface IEdgeDaemon Task UninstallAsync(CancellationToken token); - Task WaitForStatusAsync(EdgeDaemonStatus desired, CancellationToken token); + string GetCertificatesPath(); + + IotedgeCli GetCli(); } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/IOsPlatform.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/IOsPlatform.cs index 583d13d0629..903d1a74348 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/IOsPlatform.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/IOsPlatform.cs @@ -14,19 +14,17 @@ public interface IOsPlatform { Task CollectDaemonLogsAsync(DateTime testStartTime, string filePrefix, CancellationToken token); - Task CreateEdgeDaemonAsync(CancellationToken token); + Task CreateEdgeDaemonAsync(Option packagesPath, CancellationToken token); // After calling this function, the following files will be available under {scriptPath}: // certs/iot-device-{deviceId}-full-chain.cert.pem // private/iot-device-{deviceId}.key.pem - Task GenerateIdentityCertificatesAsync(string deviceId, string scriptPath, CancellationToken token); + Task GenerateIdentityCertificatesAsync(string deviceId, string scriptPath, string destPath, CancellationToken token); // After calling this function, the following files will be available under {scriptPath}: // certs/iot-edge-device-{deviceId}-full-chain.cert.pem // private/iot-edge-device-{deviceId}.key.pem - Task GenerateCaCertificatesAsync(string deviceId, string scriptPath, CancellationToken token); - - CaCertificates GetEdgeQuickstartCertificates(string deviceId); + Task GenerateCaCertificatesAsync(string deviceId, string scriptPath, string destPath, CancellationToken token); void InstallCaCertificates(IEnumerable certs, ITransportSettings transportSettings); diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/IServiceManager.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/IServiceManager.cs new file mode 100644 index 00000000000..8ba29077ad7 --- /dev/null +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/IServiceManager.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +namespace Microsoft.Azure.Devices.Edge.Test.Common +{ + using System.Threading; + using System.Threading.Tasks; + + public interface IServiceManager + { + Task StartAsync(CancellationToken token); + Task StopAsync(CancellationToken token); + Task ConfigureAsync(CancellationToken token); + string ConfigurationPath(); + string GetCliName(); + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/IotedgeCli.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/IotedgeCli.cs new file mode 100644 index 00000000000..fd3cf3e78ee --- /dev/null +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/IotedgeCli.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. +namespace Microsoft.Azure.Devices.Edge.Test.Common +{ + using System; + using System.Threading; + using System.Threading.Tasks; + + public class IotedgeCli + { + readonly string name; + + public IotedgeCli(string name = "iotedge") + { + this.name = name; + } + + public Task RunAsync( + string args, + Action onStandardOutput, + Action onStandardError, + CancellationToken token, + bool logCommand = true) + { + return Process.RunAsync(this.name, args, onStandardOutput, onStandardError, token, logCommand); + } + + public Task RunAsync( + string args, + CancellationToken token, + bool logCommand = true, + bool logOutput = true) + { + return Process.RunAsync(this.name, args, token, logCommand, logOutput); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/LeafDevice.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/LeafDevice.cs index 6ad4ac5e042..bd6af625d19 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/LeafDevice.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/LeafDevice.cs @@ -42,6 +42,7 @@ public static Task CreateAsync( Option parentId, bool useSecondaryCertificate, CertificateAuthority ca, + string certsPath, IotHub iotHub, string edgeHostname, CancellationToken token, @@ -76,6 +77,7 @@ public static Task CreateAsync( leafDeviceId, p, ca, + certsPath, iotHub, transport, edgeHostname, @@ -91,6 +93,7 @@ public static Task CreateAsync( p, useSecondaryCertificate, ca, + certsPath, iotHub, transport, edgeHostname, @@ -165,6 +168,7 @@ static async Task CreateWithCaCertAsync( string leafDeviceId, string parentId, CertificateAuthority ca, + string certsPath, IotHub iotHub, ITransportSettings transport, string edgeHostname, @@ -190,7 +194,7 @@ static async Task CreateWithCaCertAsync( token, async () => { - IdCertificates certFiles = await ca.GenerateIdentityCertificatesAsync(leafDeviceId, token); + var certFiles = await ca.GenerateIdentityCertificatesAsync(leafDeviceId, certsPath, token); (X509Certificate2 leafCert, IEnumerable trustedCerts) = CertificateHelper.GetServerCertificateAndChainFromFile(certFiles.CertificatePath, certFiles.KeyPath); @@ -216,14 +220,15 @@ static async Task CreateWithSelfSignedCertAsync( string parentId, bool useSecondaryCertificate, CertificateAuthority ca, + string certsPath, IotHub iotHub, ITransportSettings transport, string edgeHostname, CancellationToken token, ClientOptions options) { - IdCertificates primary = await ca.GenerateIdentityCertificatesAsync($"{leafDeviceId}-1", token); - IdCertificates secondary = await ca.GenerateIdentityCertificatesAsync($"{leafDeviceId}-2", token); + var primary = await ca.GenerateIdentityCertificatesAsync($"{leafDeviceId}-1", certsPath, token); + var secondary = await ca.GenerateIdentityCertificatesAsync($"{leafDeviceId}-2", certsPath, token); string[] streams = await Task.WhenAll( new[] diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/OsPlatform.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/OsPlatform.cs index 9aac30bd2e4..0346eb0f5f5 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/OsPlatform.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/OsPlatform.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Test.Common { + using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -20,12 +21,6 @@ public class OsPlatform public static bool IsArm() => RuntimeInformation.OSArchitecture == Architecture.Arm || RuntimeInformation.OSArchitecture == Architecture.Arm64; - public CaCertificates GetEdgeQuickstartCertificates(string deviceId) => - new CaCertificates( - FixedPaths.QuickStartCaCert.Cert(deviceId), - FixedPaths.QuickStartCaCert.Key(deviceId), - FixedPaths.QuickStartCaCert.TrustCert(deviceId)); - protected async Task InstallRootCertificateAsync( string basePath, (string name, string args) command, @@ -64,15 +59,42 @@ protected async Task RunScriptAsync( static void CheckFiles(IEnumerable paths, string basePath) => NormalizeFiles(paths, basePath); - public static string[] NormalizeFiles(IEnumerable paths, string basePath) - { - return paths.Select( - path => + public static FileInfo[] NormalizeFiles(IEnumerable paths, string basePath, bool assertExists = true) => + paths.Select(path => + { + var file = new FileInfo(Path.Combine(basePath, path)); + if (assertExists) { - var file = new FileInfo(Path.Combine(basePath, path)); Preconditions.CheckArgument(file.Exists, $"File Not Found: {file.FullName}"); - return file.FullName; - }).ToArray(); + } + + return file; + }).ToArray(); + + public static void CopyCertificates(FileInfo[] sourcePaths, FileInfo[] destinationPaths) + { + Preconditions.CheckArgument(sourcePaths.Length == destinationPaths.Length); + for (int i = 0; i < sourcePaths.Length; i++) + { + var parentDir = Directory.GetParent(destinationPaths[i].FullName); + if (!parentDir.Exists) + { + parentDir.Create(); + } + + sourcePaths[i].CopyTo(destinationPaths[i].FullName, overwrite: true); + switch (destinationPaths[i]) + { + case var path when path.Name.EndsWith("key.pem"): + OsPlatform.Current.SetOwner(path.FullName, "aziotks", "600"); + break; + case var path when path.Name.EndsWith("cert.pem"): + OsPlatform.Current.SetOwner(path.FullName, "aziotcs", "644"); + break; + case var path: + throw new NotImplementedException($"Expected file {path} to end with 'key.pem' or 'cert.pem'"); + } + } } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/Process.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/Process.cs index 95776dbdbd7..37d226808ee 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/Process.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/Process.cs @@ -2,6 +2,7 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common { using System; + using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Threading; @@ -11,7 +12,7 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common public class Process { - public static async Task RunAsync(string name, string args, Action onStandardOutput, Action onStandardError, CancellationToken token) + public static async Task RunAsync(string name, string args, Action onStandardOutput, Action onStandardError, CancellationToken token, bool logCommand = true) { var info = new ProcessStartInfo { @@ -20,6 +21,11 @@ public static async Task RunAsync(string name, string args, Action onSta RedirectStandardInput = true, }; + if (logCommand) + { + Log.Verbose($"RunAsync: {name} {args}"); + } + using (ProcessResults result = await ProcessEx.RunAsync(info, onStandardOutput, onStandardError, token)) { if (result.ExitCode != 0) @@ -29,7 +35,28 @@ public static async Task RunAsync(string name, string args, Action onSta } } - public static async Task RunAsync(string name, string args, CancellationToken token, bool logVerbose = true) + static async Task RunAsync(ProcessStartInfo processStartInfo, CancellationToken token, bool logOutput) + { + Action MakeOutputHandler(bool logOutput) + { + return logOutput ? (string s) => Log.Verbose(s) : (string o) => { }; + } + + Action onStdout = MakeOutputHandler(logOutput); + Action onStderr = MakeOutputHandler(logOutput); + + using (ProcessResults result = await ProcessEx.RunAsync(processStartInfo, onStdout, onStderr, token)) + { + if (result.ExitCode != 0) + { + throw new Win32Exception(result.ExitCode); + } + + return result.StandardOutput; + } + } + + public static async Task RunAsync(string name, string args, CancellationToken token, bool logCommand = true, bool logOutput = true) { var info = new ProcessStartInfo { @@ -38,28 +65,33 @@ public static async Task RunAsync(string name, string args, Cancellati RedirectStandardInput = true, }; - if (logVerbose) + if (logCommand) { Log.Verbose($"RunAsync: {name} {args}"); } - Action MakeOutputHandler(bool logVerbose) - { - return logVerbose ? (string s) => Log.Verbose(s) : (string o) => { }; - } + return await RunAsync(info, token, logOutput); + } - Action onStdout = MakeOutputHandler(logVerbose); - Action onStderr = MakeOutputHandler(logVerbose); + public static async Task RunAsync(string name, ICollection args, CancellationToken token, bool logCommand = true, bool logOutput = true) + { + var info = new ProcessStartInfo + { + FileName = name, + RedirectStandardInput = true, + }; - using (ProcessResults result = await ProcessEx.RunAsync(info, onStdout, onStderr, token)) + foreach (var arg in args) { - if (result.ExitCode != 0) - { - throw new Win32Exception(result.ExitCode); - } + info.ArgumentList.Add(arg); + } - return result.StandardOutput; + if (logCommand) + { + Log.Verbose($"RunAsync: {name} {string.Join(' ', args)}"); } + + return await RunAsync(info, token, logOutput); } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/CaCertificates.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/CaCertificates.cs index a21a26252c8..4e9e0d024e3 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/CaCertificates.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/CaCertificates.cs @@ -1,15 +1,25 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Test.Common.Certs { + using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography.X509Certificates; using Microsoft.Azure.Devices.Edge.Util; - public class CaCertificates : IdCertificates + public class CaCertificates { + static string[] GetFileLocations(string deviceId) => + new[] + { + FixedPaths.DeviceCaCert.Cert(deviceId), + FixedPaths.DeviceCaCert.Key(deviceId), + FixedPaths.DeviceCaCert.TrustCert + }; + + public string CertificatePath { get; } + public string KeyPath { get; } public string TrustedCertificatesPath { get; } - public Option> ContentTrustInputs { get; } public IEnumerable TrustedCertificates => new[] @@ -17,33 +27,33 @@ public class CaCertificates : IdCertificates new X509Certificate2(X509Certificate.CreateFromCertFile(this.TrustedCertificatesPath)) }; - string[] GetEdgeCertFileLocation(string deviceId) + public CaCertificates(string deviceId, string scriptPath) { - return new[] - { - FixedPaths.DeviceCaCert.Cert(deviceId), - FixedPaths.DeviceCaCert.Key(deviceId), - FixedPaths.DeviceCaCert.TrustCert - }; + var locations = GetFileLocations(deviceId); + var files = OsPlatform.NormalizeFiles(locations, scriptPath); + this.CertificatePath = files[0].FullName; + this.KeyPath = files[1].FullName; + this.TrustedCertificatesPath = files[2].FullName; } - public CaCertificates(string deviceId, string scriptPath) + public CaCertificates(FileInfo certificatePath, FileInfo keyPath, FileInfo trustedCertsPath) { - var location = this.GetEdgeCertFileLocation(deviceId); - var files = OsPlatform.NormalizeFiles(location, scriptPath); - this.CertificatePath = files[0]; - this.KeyPath = files[1]; - this.TrustedCertificatesPath = files[2]; + Preconditions.CheckArgument(certificatePath.Exists); + Preconditions.CheckArgument(keyPath.Exists); + Preconditions.CheckArgument(trustedCertsPath.Exists); + this.CertificatePath = certificatePath.FullName; + this.KeyPath = keyPath.FullName; + this.TrustedCertificatesPath = trustedCertsPath.FullName; } - public CaCertificates(string certificatePath, string keyPath, string trustedCertsPath) + // Move certs/keys out of default directory so they aren't overwritten + public static CaCertificates CopyTo(string deviceId, string scriptPath, string destPath) { - Preconditions.CheckArgument(File.Exists(certificatePath)); - Preconditions.CheckArgument(File.Exists(keyPath)); - Preconditions.CheckArgument(File.Exists(trustedCertsPath)); - this.CertificatePath = certificatePath; - this.KeyPath = keyPath; - this.TrustedCertificatesPath = trustedCertsPath; + var paths = GetFileLocations(deviceId); + var sourcePaths = OsPlatform.NormalizeFiles(paths, scriptPath); + var destinationPaths = OsPlatform.NormalizeFiles(paths, destPath, assertExists: false); + OsPlatform.CopyCertificates(sourcePaths, destinationPaths); + return new CaCertificates(destinationPaths[0], destinationPaths[1], destinationPaths[2]); } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/CertificateAuthority.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/CertificateAuthority.cs index 44d9f34c0e1..274c80c91a5 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/CertificateAuthority.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/CertificateAuthority.cs @@ -1,17 +1,16 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Test.Common.Certs { - using System; using System.IO; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util; using Serilog; using RootCaKeys = System.ValueTuple; public class CertificateAuthority { - readonly Option scriptPath; + readonly string scriptPath; + readonly string deviceId; // TODO: EdgeCertificates needs to be moved into certificate types. public CaCertificates EdgeCertificates { get; set; } @@ -33,38 +32,19 @@ public static async Task CreateAsync(string deviceId, Root Log.Verbose("----------------------------------------"); } - return new CertificateAuthority(scriptPath); + return new CertificateAuthority(deviceId, scriptPath); } - public static CertificateAuthority GetQuickstart(string deviceId) + CertificateAuthority(string deviceId, string scriptPath) { - CaCertificates certs = OsPlatform.Current.GetEdgeQuickstartCertificates(deviceId); - return new CertificateAuthority(certs); + this.deviceId = deviceId; + this.scriptPath = scriptPath; } - CertificateAuthority(string scriptPath) - { - this.scriptPath = Option.Maybe(scriptPath); - } - - CertificateAuthority(CaCertificates certs) - { - this.scriptPath = Option.None(); - this.EdgeCertificates = certs; - } + public Task GenerateIdentityCertificatesAsync(string uniqueId, string destPath, CancellationToken token) => + OsPlatform.Current.GenerateIdentityCertificatesAsync(uniqueId, this.scriptPath, destPath, token); - public Task GenerateIdentityCertificatesAsync(string deviceId, CancellationToken token) - { - const string Err = "Cannot generate certificates without script"; - string scriptPath = this.scriptPath.Expect(() => new InvalidOperationException(Err)); - return OsPlatform.Current.GenerateIdentityCertificatesAsync(deviceId, scriptPath, token); - } - - public Task GenerateCaCertificatesAsync(string deviceId, CancellationToken token) - { - const string Err = "Cannot generate certificates without script"; - string scriptPath = this.scriptPath.Expect(() => new InvalidOperationException(Err)); - return OsPlatform.Current.GenerateCaCertificatesAsync(deviceId, scriptPath, token); - } + public Task GenerateCaCertificatesAsync(string destPath, CancellationToken token) => + OsPlatform.Current.GenerateCaCertificatesAsync(this.deviceId, this.scriptPath, destPath, token); } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/FixedPaths.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/FixedPaths.cs index 45d190efcdf..49be24f801c 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/FixedPaths.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/FixedPaths.cs @@ -5,9 +5,6 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common.Certs public sealed class FixedPaths { - // Directory used by a test run to store temporary keys, certs, etc. - public const string E2E_TEST_DIR = "/etc/aziot/e2e_tests"; - public sealed class DeviceIdentityCert { public static string Cert(string deviceId) => $"certs/iot-device-{deviceId}-full-chain.cert.pem"; @@ -26,14 +23,5 @@ public sealed class RootCaCert public const string Cert = "certs/azure-iot-test-only.root.ca.cert.pem"; public const string Key = "private/azure-iot-test-only.root.ca.key.pem"; } - - public sealed class QuickStartCaCert - { - private static string BasePath(string deviceId) => Path.Combine(E2E_TEST_DIR, deviceId); - - public static string Cert(string deviceId) => Path.Combine(BasePath(deviceId), "device_ca_cert.pem"); - public static string Key(string deviceId) => Path.Combine(BasePath(deviceId), "device_ca_cert_key.pem"); - public static string TrustCert(string deviceId) => Path.Combine(BasePath(deviceId), "trust_bundle.pem"); - } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/IdCertificates.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/IdCertificates.cs index 510380e208a..617a4d14d3d 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/IdCertificates.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/IdCertificates.cs @@ -1,31 +1,48 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Test.Common.Certs { + using System.IO; + using System.Security.Cryptography.X509Certificates; + using Microsoft.Azure.Devices.Edge.Util; + public class IdCertificates { - string[] GetFileLocation(string deviceId) - { - return new[] + static string[] GetFileLocations(string deviceId) => + new[] { FixedPaths.DeviceIdentityCert.Cert(deviceId), FixedPaths.DeviceIdentityCert.Key(deviceId) }; - } public string CertificatePath { get; protected set; } - public string KeyPath { get; protected set; } - protected IdCertificates() + public X509Certificate2 Certificate => new X509Certificate2(this.CertificatePath); + + public IdCertificates(string deviceId, string scriptPath) { + var locations = GetFileLocations(deviceId); + var files = OsPlatform.NormalizeFiles(locations, scriptPath); + this.CertificatePath = files[0].FullName; + this.KeyPath = files[1].FullName; } - public IdCertificates(string deviceId, string scriptPath) + public IdCertificates(FileInfo certificatePath, FileInfo keyPath) + { + Preconditions.CheckArgument(certificatePath.Exists); + Preconditions.CheckArgument(keyPath.Exists); + this.CertificatePath = certificatePath.FullName; + this.KeyPath = keyPath.FullName; + } + + // Move certs/keys out of default directory so they aren't overwritten + public static IdCertificates CopyTo(string deviceId, string scriptPath, string destPath) { - var location = this.GetFileLocation(deviceId); - var files = OsPlatform.NormalizeFiles(location, scriptPath); - this.CertificatePath = files[0]; - this.KeyPath = files[1]; + var paths = GetFileLocations(deviceId); + var sourcePaths = OsPlatform.NormalizeFiles(paths, scriptPath); + var destinationPaths = OsPlatform.NormalizeFiles(paths, destPath, assertExists: false); + OsPlatform.CopyCertificates(sourcePaths, destinationPaths); + return new IdCertificates(destinationPaths[0], destinationPaths[1]); } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/TestCertificates.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/TestCertificates.cs new file mode 100644 index 00000000000..47884929f6a --- /dev/null +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/certs/TestCertificates.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. +namespace Microsoft.Azure.Devices.Edge.Test.Common.Certs +{ + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Devices.Edge.Test.Common; + + // Generates a test CA cert, test CA key, and trust bundle. + // TODO: Remove this once iotedge init is finished? + public class TestCertificates + { + public static async Task GenerateIdentityCertificatesAsync( + string deviceId, + string destPath, + CancellationToken token) + { + string scriptPath = Context.Current.CaCertScriptPath.Expect( + () => new System.InvalidOperationException("Missing CA cert script path (check caCertScriptPath in context.json)")); + (string, string, string) rootCa = Context.Current.RootCaKeys.Expect( + () => new System.InvalidOperationException("Missing root CA")); + + CertificateAuthority ca = await CertificateAuthority.CreateAsync(deviceId, rootCa, scriptPath, token); + return await ca.GenerateIdentityCertificatesAsync(deviceId, destPath, token); + } + + public static async Task<(CaCertificates, CertificateAuthority ca)> GenerateEdgeCaCertsAsync( + string deviceId, + string destPath, + CancellationToken token) + { + string scriptPath = Context.Current.CaCertScriptPath.Expect( + () => new System.InvalidOperationException("Missing CA cert script path (check caCertScriptPath in context.json)")); + (string, string, string) rootCa = Context.Current.RootCaKeys.Expect( + () => new System.InvalidOperationException("Missing root CA")); + + CertificateAuthority ca = await CertificateAuthority.CreateAsync(deviceId, rootCa, scriptPath, token); + CaCertificates certs = await ca.GenerateCaCertificatesAsync(destPath, token); + ca.EdgeCertificates = certs; + + return (certs, ca); + } + } +} diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/EdgeDaemon.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/EdgeDaemon.cs index 557c036b30a..fa6c819913d 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/EdgeDaemon.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/EdgeDaemon.cs @@ -3,9 +3,9 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common.Linux { using System; using System.ComponentModel; + using System.IO; using System.Linq; using System.Net; - using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util; @@ -14,9 +14,12 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common.Linux public class EdgeDaemon : IEdgeDaemon { readonly PackageManagement packageManagement; + readonly Option packagesPath; + readonly IServiceManager serviceManager; readonly bool isCentOs; + readonly string certsPath; - public static async Task CreateAsync(CancellationToken token) + public static async Task CreateAsync(Option packagesPath, CancellationToken token) { string[] platformInfo = await Process.RunAsync("cat", @"/etc/os-release", token); string os = Array.Find(platformInfo, element => element.StartsWith("ID=")); @@ -39,12 +42,17 @@ public static async Task CreateAsync(CancellationToken token) // Split potential version description (in case VERSION_ID was not available, the VERSION line can contain e.g. '7 (Core)') version = version.Split('=').Last().Split(' ').First().Trim(trimChr); + bool detectedSnap = packagesPath.Map(path => Directory.GetFiles(path, $"*.snap").Length != 0).OrDefault(); + SupportedPackageExtension packageExtension; switch (os) { case "ubuntu": - packageExtension = SupportedPackageExtension.Deb; + // if we find .deb and .snap files on an Ubuntu 22.04 host, prefer snap + packageExtension = detectedSnap && version == "22.04" + ? SupportedPackageExtension.Snap + : SupportedPackageExtension.Deb; break; case "raspbian": os = "debian"; @@ -57,7 +65,7 @@ public static async Task CreateAsync(CancellationToken token) if (version != "8" && version != "9") { - throw new NotImplementedException($"Daemon is only installed on Red Hat version 8.X, operating system '{os} {version}'"); + throw new NotImplementedException($"Operating system '{os} {version}' not supported"); } break; @@ -67,7 +75,7 @@ public static async Task CreateAsync(CancellationToken token) if (version != "7") { - throw new NotImplementedException($"Daemon is only installed on Centos version 7.X, operating system '{os} {version}'"); + throw new NotImplementedException($"Operating system '{os} {version}' not supported"); } break; @@ -78,27 +86,38 @@ public static async Task CreateAsync(CancellationToken token) throw new NotImplementedException($"Don't know how to install daemon on operating system '{os}'"); } - return new EdgeDaemon(new PackageManagement(os, version, packageExtension), os == "centos"); + if (detectedSnap && packageExtension != SupportedPackageExtension.Snap) + { + throw new NotImplementedException( + $"Snap package was detected but isn't supported on operating system '{os} {version}'"); + } + + return new EdgeDaemon(packagesPath, new PackageManagement(os, version, packageExtension), os == "centos"); } - EdgeDaemon(PackageManagement packageManagement, bool isCentOs) + EdgeDaemon(Option packagesPath, PackageManagement packageManagement, bool isCentOs) { + this.packagesPath = packagesPath; this.packageManagement = packageManagement; + this.serviceManager = packageManagement.PackageExtension == SupportedPackageExtension.Snap + ? new SnapServiceManager() + : new SystemdServiceManager(); this.isCentOs = isCentOs; + this.certsPath = Path.Combine(Path.GetDirectoryName(this.serviceManager.ConfigurationPath()), "e2e_tests"); } - public async Task InstallAsync(Option packagesPath, Option proxy, CancellationToken token) + public async Task InstallAsync(Option proxy, CancellationToken token) { var properties = new object[] { Dns.GetHostName() }; string message = "Installed edge daemon on '{Device}'"; - packagesPath.ForEach( + this.packagesPath.ForEach( p => { message += " from packages in '{InstallPackagePath}'"; properties = properties.Append(p).ToArray(); }); - string[] commands = packagesPath.Match( + string[] commands = this.packagesPath.Match( p => this.packageManagement.GetInstallCommandsFromLocal(p), () => this.packageManagement.GetInstallCommandsFromMicrosoftProd(proxy)); @@ -112,7 +131,10 @@ await Profiler.Run( properties); } - public Task ConfigureAsync(Func> config, CancellationToken token, bool restart) + public Task ConfigureAsync( + Func> config, + CancellationToken token, + bool restart) { var properties = new object[] { }; var message = "Configured edge daemon"; @@ -122,7 +144,7 @@ public Task ConfigureAsync(Func> c { await this.InternalStopAsync(token); - DaemonConfiguration conf = new DaemonConfiguration("/etc/aziot/config.toml"); + var conf = new DaemonConfiguration(this.serviceManager.ConfigurationPath()); if (this.isCentOs) { // The recommended way to set up [listen] sockets in config.toml is to use the 'fd://...' URL @@ -132,14 +154,20 @@ public Task ConfigureAsync(Func> c conf.SetListenSockets("unix:///var/lib/iotedge/workload.sock", "unix:///var/lib/iotedge/mgmt.sock"); } - (string msg, object[] props) = await config(conf); + if (this.packageManagement.PackageExtension == SupportedPackageExtension.Snap) + { + conf.SetDeviceHomedir("/var/snap/azure-iot-edge/common/var/lib/aziot/edged"); + conf.SetMobyRuntimeUri("unix:///var/snap/azure-iot-edge/common/docker-proxy.sock"); + conf.AddAgentUserId("0"); + } + (string msg, object[] props) = await config(conf); message += $" {msg}"; properties = properties.Concat(props).ToArray(); if (restart) { - await Process.RunAsync("iotedge", "config apply", token); + await this.serviceManager.ConfigureAsync(token); } }, message.ToString(), @@ -152,14 +180,13 @@ public Task StartAsync(CancellationToken token) => Profiler.Run( async Task InternalStartAsync(CancellationToken token) { - await Process.RunAsync("systemctl", "start aziot-keyd aziot-certd aziot-identityd aziot-edged", token); - await WaitForStatusAsync(ServiceControllerStatus.Running, token); + await this.serviceManager.StartAsync(token); await Task.Delay(10000); await Retry.Do( async () => { - string[] output = await Process.RunAsync("iotedge", "list", token); + string[] output = await this.GetCli().RunAsync("list", token); return output; }, output => true, @@ -188,8 +215,7 @@ public Task StopAsync(CancellationToken token) => Profiler.Run( async Task InternalStopAsync(CancellationToken token) { - await Process.RunAsync("systemctl", $"stop {this.packageManagement.IotedgeServices}", token); - await WaitForStatusAsync(ServiceControllerStatus.Stopped, token); + await this.serviceManager.StopAsync(token); } public async Task UninstallAsync(CancellationToken token) @@ -222,35 +248,11 @@ await Profiler.Run( }, "Uninstalled edge daemon"); } - public Task WaitForStatusAsync(EdgeDaemonStatus desired, CancellationToken token) => Profiler.Run( - () => WaitForStatusAsync((ServiceControllerStatus)desired, token), - "Edge daemon entered the '{Desired}' state", - desired.ToString().ToLower()); + public string GetCertificatesPath() => this.certsPath; - static async Task WaitForStatusAsync(ServiceControllerStatus desired, CancellationToken token) + public IotedgeCli GetCli() { - string[] processes = { "aziot-keyd", "aziot-certd", "aziot-identityd", "aziot-edged" }; - - foreach (string process in processes) - { - while (true) - { - Func stateMatchesDesired = desired switch - { - ServiceControllerStatus.Running => s => s == "active", - ServiceControllerStatus.Stopped => s => s == "inactive" || s == "failed", - _ => throw new NotImplementedException($"No handler for {desired}"), - }; - - string[] output = await Process.RunAsync("systemctl", $"-p ActiveState show {process}", token); - if (stateMatchesDesired(output.First().Split("=").Last())) - { - break; - } - - await Task.Delay(250, token).ConfigureAwait(false); - } - } + return new IotedgeCli(this.serviceManager.GetCliName()); } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/OsPlatform.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/OsPlatform.cs index 8a461d7465c..4e71976eccc 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/OsPlatform.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/OsPlatform.cs @@ -24,7 +24,7 @@ public async Task CollectDaemonLogsAsync(DateTime testStartTime, string "-u docker", $"--since \"{testStartTime:yyyy-MM-dd HH:mm:ss}\"", "--no-pager"); - string[] output = await Process.RunAsync("journalctl", args, token, logVerbose: false); + string[] output = await Process.RunAsync("journalctl", args, token, logCommand: true, logOutput: false); string daemonLog = $"{filePrefix}-daemon.log"; await File.WriteAllLinesAsync(daemonLog, output, token); @@ -32,21 +32,21 @@ public async Task CollectDaemonLogsAsync(DateTime testStartTime, string return daemonLog; } - public async Task CreateEdgeDaemonAsync(CancellationToken token) => - await EdgeDaemon.CreateAsync(token); + public async Task CreateEdgeDaemonAsync(Option packagesPath, CancellationToken token) => + await EdgeDaemon.CreateAsync(packagesPath, token); - public async Task GenerateIdentityCertificatesAsync(string deviceId, string scriptPath, CancellationToken token) + public async Task GenerateIdentityCertificatesAsync(string uniqueId, string scriptPath, string destPath, CancellationToken token) { - var command = BuildCertCommand($"create_device_certificate '{deviceId}'", scriptPath); + var command = BuildCertCommand($"create_device_certificate '{uniqueId}'", scriptPath); await this.RunScriptAsync(("bash", command), token); - return new IdCertificates(deviceId, scriptPath); + return IdCertificates.CopyTo(uniqueId, scriptPath, destPath); } - public async Task GenerateCaCertificatesAsync(string deviceId, string scriptPath, CancellationToken token) + public async Task GenerateCaCertificatesAsync(string deviceId, string scriptPath, string destPath, CancellationToken token) { var command = BuildCertCommand($"create_edge_device_certificate '{deviceId}'", scriptPath); await this.RunScriptAsync(("bash", command), token); - return new CaCertificates(deviceId, scriptPath); + return CaCertificates.CopyTo(deviceId, scriptPath, destPath); } public void InstallCaCertificates(IEnumerable certs, ITransportSettings transportSettings) => diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/PackageManagement.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/PackageManagement.cs index 89569ea513f..2cb441168e5 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/PackageManagement.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/PackageManagement.cs @@ -9,7 +9,8 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common.Linux public enum SupportedPackageExtension { Deb, - Rpm + Rpm, + Snap } public class PackageManagement @@ -17,29 +18,24 @@ public class PackageManagement readonly string os; readonly string version; readonly SupportedPackageExtension packageExtension; - public string IotedgeServices { get; } public PackageManagement(string os, string version, SupportedPackageExtension extension) { this.os = os; this.version = version; this.packageExtension = extension; - this.IotedgeServices = string.Join( - " ", - "aziot-keyd.service", - "aziot-certd.service", - "aziot-identityd.service", - "aziot-edged.service"); } + public SupportedPackageExtension PackageExtension => this.packageExtension; + public string[] GetInstallCommandsFromLocal(string path) { string[] packages = Directory - .GetFiles(path, $"*.{this.packageExtension.ToString().ToLower()}") + .GetFiles(path, $"*.{this.PackageExtension.ToString().ToLower()}") .Where(p => !p.Contains("debug") && !p.Contains("devel")) .ToArray(); - return this.packageExtension switch + return this.PackageExtension switch { SupportedPackageExtension.Deb => new[] { @@ -79,7 +75,27 @@ public string[] GetInstallCommandsFromLocal(string path) }, _ => throw new NotImplementedException($"RPM packaging is set up only for Centos, Mariner, and RHEL, current OS '.{this.os}'"), }, - _ => throw new NotImplementedException($"Don't know how to install daemon on for '.{this.packageExtension}'"), + SupportedPackageExtension.Snap => new[] + { + "set -e", + $"snap install {string.Join(' ', packages)} --dangerous", + "snap connect azure-iot-identity:hostname-control", + "snap connect azure-iot-identity:log-observe", + "snap connect azure-iot-identity:mount-observe", + "snap connect azure-iot-identity:system-observe", + // There isn't a TPM in this setup, so don't connect it + // "snap connect azure-iot-identity:tpm", + "snap connect azure-iot-edge:home", + "snap connect azure-iot-edge:hostname-control", + "snap connect azure-iot-edge:log-observe", + "snap connect azure-iot-edge:mount-observe", + "snap connect azure-iot-edge:system-observe", + "snap connect azure-iot-edge:run-iotedge", + "snap connect azure-iot-edge:aziotctl-executables azure-iot-identity:aziotctl-executables", + "snap connect azure-iot-edge:identity-service azure-iot-identity:identity-service", + "snap connect azure-iot-edge:docker docker:docker-daemon" + }, + _ => throw new NotImplementedException($"Don't know how to install daemon on for '.{this.PackageExtension}'"), }; } @@ -93,7 +109,7 @@ public string[] GetInstallCommandsFromMicrosoftProd(Option proxy) _ => throw new NotImplementedException($"Don't know how to install daemon for '{this.os}'"), }; - return this.packageExtension switch + return this.PackageExtension switch { SupportedPackageExtension.Deb => new[] { @@ -127,11 +143,11 @@ public string[] GetInstallCommandsFromMicrosoftProd(Option proxy) }, _ => throw new NotImplementedException($"Don't know how to install daemon on for '.{this.os}'") }, - _ => throw new NotImplementedException($"Don't know how to install daemon on for '.{this.packageExtension}'"), + _ => throw new NotImplementedException($"Don't know how to install daemon on for '.{this.PackageExtension}'"), }; } - public string[] GetUninstallCommands() => this.packageExtension switch + public string[] GetUninstallCommands() => this.PackageExtension switch { SupportedPackageExtension.Deb => new[] { @@ -153,7 +169,14 @@ public string[] GetInstallCommandsFromMicrosoftProd(Option proxy) "yum autoremove -y", "systemctl restart docker" // we can remove after this is fixed (https://github.com/moby/moby/issues/23302) }, - _ => throw new NotImplementedException($"Don't know how to uninstall daemon on for '.{this.packageExtension}'") + SupportedPackageExtension.Snap => new string[] + { + "snap remove --purge azure-iot-edge", + "snap remove --purge azure-iot-identity", + "rm -r -f /etc/aziot", + "snap restart docker" // we can remove after this is fixed (https://github.com/moby/moby/issues/23302) + }, + _ => throw new NotImplementedException($"Don't know how to uninstall daemon on for '.{this.PackageExtension}'") }; } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/SnapServiceManager.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/SnapServiceManager.cs new file mode 100644 index 00000000000..2189183b836 --- /dev/null +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/SnapServiceManager.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft. All rights reserved. +namespace Microsoft.Azure.Devices.Edge.Test.Common.Linux +{ + using System; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Serilog; + + class SnapServiceManager : IServiceManager + { + enum ServiceStatus + { + Running, + Stopped + } + + public async Task StartAsync(CancellationToken token) + { + await Process.RunAsync("snap", $"start azure-iot-edge.aziot-edged", token); + await this.WaitForStatusAsync(ServiceStatus.Running, token); + } + + public async Task StopAsync(CancellationToken token) + { + await Process.RunAsync("snap", $"stop azure-iot-edge.aziot-edged", token); + await this.WaitForStatusAsync(ServiceStatus.Stopped, token); + } + + public async Task ConfigureAsync(CancellationToken token) + { + var config = await File.ReadAllTextAsync(this.ConfigurationPath(), token); + // Turn off verbose logging when setting config to avoid logging sensitive information, like docker + // registry credentials. + Log.Verbose($"Calling 'snap set azure-iot-edge raw-config=...' with contents of {this.ConfigurationPath()}"); + await Process.RunAsync( + "snap", + new string[] { "set", "azure-iot-edge", $"raw-config={config}" }, + token, + logCommand: false, + logOutput: true); + + // `snap set azure-iot-edge raw-config=...` calls `iotedge config apply`, which, for snaps, only restarts + // aziot-edged, not identityd, keyd, certd, or tpmd. The identity service components need to refresh their + // config, so we'll force-restart them. + await Process.RunAsync("snap", "stop azure-iot-edge.aziot-edged", token); + await Process.RunAsync("snap", "restart azure-iot-identity", token); + await Process.RunAsync("snap", "start azure-iot-edge.aziot-edged", token); + } + + public string ConfigurationPath() => + "/var/snap/azure-iot-identity/current/shared/config/aziot/config-e2e.toml"; + public string GetCliName() => "azure-iot-edge.iotedge"; + + async Task WaitForStatusAsync(ServiceStatus desired, CancellationToken token) + { + Func stateMatchesDesired = desired switch + { + ServiceStatus.Running => s => s == "active", + ServiceStatus.Stopped => s => s == "inactive", + _ => throw new NotImplementedException($"No handler for {desired}"), + }; + + while (true) + { + string[] output = await Process.RunAsync("snap", "services azure-iot-edge.aziot-edged", token); + string state = output.Last().Split(" ", StringSplitOptions.RemoveEmptyEntries)[2]; + if (stateMatchesDesired(state)) + { + break; + } + + await Task.Delay(250, token).ConfigureAwait(false); + } + } + } +} diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/SystemdServiceManager.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/SystemdServiceManager.cs new file mode 100644 index 00000000000..84fb903ce04 --- /dev/null +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/linux/SystemdServiceManager.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All rights reserved. +namespace Microsoft.Azure.Devices.Edge.Test.Common.Linux +{ + using System; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + class SystemdServiceManager : IServiceManager + { + enum ServiceStatus + { + Running, + Stopped + } + + readonly string[] names = { "aziot-keyd", "aziot-certd", "aziot-identityd", "aziot-edged" }; + + public async Task StartAsync(CancellationToken token) + { + await Process.RunAsync("systemctl", $"start {string.Join(' ', this.names)}", token); + await this.WaitForStatusAsync(ServiceStatus.Running, token); + } + + public async Task StopAsync(CancellationToken token) + { + await Process.RunAsync("systemctl", $"stop {string.Join(' ', this.names)}", token); + await this.WaitForStatusAsync(ServiceStatus.Stopped, token); + } + + public async Task ConfigureAsync(CancellationToken token) + { + await Process.RunAsync("iotedge", "config apply", token); + } + + public string ConfigurationPath() => "/etc/aziot/config.toml"; + public string GetCliName() => "iotedge"; + + async Task WaitForStatusAsync(ServiceStatus desired, CancellationToken token) + { + foreach (string service in this.names) + { + while (true) + { + Func stateMatchesDesired = desired switch + { + ServiceStatus.Running => s => s == "active", + ServiceStatus.Stopped => s => s == "inactive" || s == "failed", + _ => throw new NotImplementedException($"No handler for {desired}"), + }; + + string[] output = await Process.RunAsync("systemctl", $"-p ActiveState show {service}", token); + if (stateMatchesDesired(output.First().Split("=").Last())) + { + break; + } + + await Task.Delay(250, token).ConfigureAwait(false); + } + } + } + } +} diff --git a/test/Microsoft.Azure.Devices.Edge.Test/Device.cs b/test/Microsoft.Azure.Devices.Edge.Test/Device.cs index f405ae25063..6f6e5bf8f1c 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/Device.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/Device.cs @@ -22,7 +22,7 @@ public async Task QuickstartCerts() { CancellationToken token = this.TestToken; - await this.runtime.DeployConfigurationAsync(token, this.device.NestedEdge.IsNestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, this.device.NestedEdge.IsNestedEdge); string leafDeviceId = DeviceId.Current.Generate(); @@ -33,6 +33,7 @@ public async Task QuickstartCerts() Option.Some(this.runtime.DeviceId), false, this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, this.device.NestedEdge.DeviceHostname, token, @@ -61,7 +62,7 @@ public async Task QuickstartChangeSasKey() { CancellationToken token = this.TestToken; - await this.runtime.DeployConfigurationAsync(token, this.device.NestedEdge.IsNestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, this.device.NestedEdge.IsNestedEdge); string leafDeviceId = DeviceId.Current.Generate(); @@ -73,6 +74,7 @@ public async Task QuickstartChangeSasKey() Option.Some(this.runtime.DeviceId), false, this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, this.device.NestedEdge.DeviceHostname, token, @@ -102,6 +104,7 @@ await TryFinally.DoAsync( Option.Some(this.runtime.DeviceId), false, this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, this.device.NestedEdge.DeviceHostname, token, @@ -131,7 +134,7 @@ public async Task RouteMessageL3LeafToL4Module() { CancellationToken token = this.TestToken; - await this.runtime.DeployConfigurationAsync(token, Context.Current.NestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, Context.Current.NestedEdge); // These must match the IDs in nestededge_middleLayerBaseDeployment_amqp.json, // which defines a route that filters by deviceId to forwards the message @@ -148,6 +151,7 @@ public async Task RouteMessageL3LeafToL4Module() Option.Some(this.runtime.DeviceId), false, this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, Context.Current.Hostname.GetOrElse(Dns.GetHostName().ToLower()), token, @@ -198,7 +202,7 @@ public async Task DisableReenableParentEdge() CancellationToken token = this.TestToken; Log.Verbose("Deploying L3 Edge"); - await this.runtime.DeployConfigurationAsync(token, this.device.NestedEdge.IsNestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, this.device.NestedEdge.IsNestedEdge); // Disable the parent Edge device Log.Verbose("Disabling Edge device"); @@ -219,6 +223,7 @@ public async Task DisableReenableParentEdge() Option.Some(this.runtime.DeviceId), false, this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, this.device.NestedEdge.DeviceHostname, token, diff --git a/test/Microsoft.Azure.Devices.Edge.Test/DeviceWithCustomCertificates.cs b/test/Microsoft.Azure.Devices.Edge.Test/DeviceWithCustomCertificates.cs index 56c82b2d044..5f311963e54 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/DeviceWithCustomCertificates.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/DeviceWithCustomCertificates.cs @@ -21,7 +21,7 @@ public async Task TransparentGateway( { CancellationToken token = this.TestToken; - await this.runtime.DeployConfigurationAsync(token, this.device.NestedEdge.IsNestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, this.device.NestedEdge.IsNestedEdge); string leafDeviceId = DeviceId.Current.Generate(); @@ -39,6 +39,7 @@ public async Task TransparentGateway( parentId, testAuth.UseSecondaryCertificate(), this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, this.device.NestedEdge.DeviceHostname, token, @@ -91,7 +92,7 @@ public async Task GrandparentScopeDevice( CancellationToken token = this.TestToken; - await this.runtime.DeployConfigurationAsync(token, Context.Current.NestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, Context.Current.NestedEdge); string leafDeviceId = DeviceId.Current.Generate(); @@ -105,6 +106,7 @@ public async Task GrandparentScopeDevice( parentId, testAuth.UseSecondaryCertificate(), this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, this.device.NestedEdge.ParentHostname, token, diff --git a/test/Microsoft.Azure.Devices.Edge.Test/EdgeAgentDirectMethods.cs b/test/Microsoft.Azure.Devices.Edge.Test/EdgeAgentDirectMethods.cs index 76a3131a518..61f24694683 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/EdgeAgentDirectMethods.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/EdgeAgentDirectMethods.cs @@ -29,7 +29,7 @@ public async Task TestPing() // This is a temporary solution see ticket: 9288683 if (!Context.Current.ISA95Tag) { - await this.runtime.DeployConfigurationAsync(token, Context.Current.NestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, Context.Current.NestedEdge); } var result = await this.IotHub.InvokeMethodAsync(this.runtime.DeviceId, ConfigModuleName.EdgeAgent, new CloudToDeviceMethod("Ping", TimeSpan.FromSeconds(300), TimeSpan.FromSeconds(300)), token); @@ -52,7 +52,9 @@ await this.runtime.DeployConfigurationAsync( { builder.AddModule(moduleName, numberLoggerImage) .WithEnvironment(new[] { ("Count", count.ToString()) }); - }, token, + }, + this.cli, + token, Context.Current.NestedEdge); await Task.Delay(30000); @@ -122,7 +124,9 @@ await this.runtime.DeployConfigurationAsync( { builder.AddModule(moduleName, numberLoggerImage) .WithEnvironment(new[] { ("Count", count.ToString()) }); - }, token, + }, + this.cli, + token, Context.Current.NestedEdge); await Task.Delay(30000); @@ -151,7 +155,9 @@ await this.runtime.DeployConfigurationAsync( { builder.AddModule(moduleName, numberLoggerImage) .WithEnvironment(new[] { ("Count", count.ToString()) }); - }, token, + }, + this.cli, + token, Context.Current.NestedEdge); await Task.Delay(10000); @@ -193,7 +199,9 @@ await this.runtime.DeployConfigurationAsync( { builder.AddModule(moduleName, numberLoggerImage) .WithEnvironment(new[] { ("Count", count.ToString()) }); - }, token, + }, + this.cli, + token, Context.Current.NestedEdge); await Task.Delay(10000); @@ -233,7 +241,9 @@ await this.runtime.DeployConfigurationAsync( { builder.AddModule(moduleName, numberLoggerImage) .WithEnvironment(new[] { ("Count", count.ToString()) }); - }, token, + }, + this.cli, + token, Context.Current.NestedEdge); await Task.Delay(10000); diff --git a/test/Microsoft.Azure.Devices.Edge.Test/Image.cs b/test/Microsoft.Azure.Devices.Edge.Test/Image.cs index f436c115aa6..d2be506a62c 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/Image.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/Image.cs @@ -29,13 +29,15 @@ public async Task ImageGarbageCollection() { builder.AddModule(SensorName, sensorImage); }, + this.cli, token, Context.Current.NestedEdge); EdgeModule sensor = deployment1.Modules[SensorName]; - await sensor.WaitForStatusAsync(EdgeModuleStatus.Running, token); + await sensor.WaitForStatusAsync(EdgeModuleStatus.Running, this.cli, token); // Create second deployment without simulated temperature sensor EdgeDeployment deployment2 = await this.runtime.DeployConfigurationAsync( + this.cli, token, Context.Current.NestedEdge); diff --git a/test/Microsoft.Azure.Devices.Edge.Test/IoTEdgeCheck.cs b/test/Microsoft.Azure.Devices.Edge.Test/IoTEdgeCheck.cs index d3099955e24..d790b28745b 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/IoTEdgeCheck.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/IoTEdgeCheck.cs @@ -18,7 +18,7 @@ public async Task IotEdgeCheck() { CancellationToken token = this.TestToken; // Need to deploy edgeHub or one check will fail - await this.runtime.DeployConfigurationAsync(token, Context.Current.NestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, Context.Current.NestedEdge); string diagnosticImageName = Context.Current .DiagnosticsImage.Expect(() => new ArgumentException("Missing diagnostic image")); @@ -63,7 +63,7 @@ void OnStdout(string o) void OnStderr(string e) => Log.Verbose(e); - await Process.RunAsync("iotedge", args, OnStdout, OnStderr, token); + await this.cli.RunAsync(args, OnStdout, OnStderr, token); Assert.AreEqual(string.Empty, errors_number); } diff --git a/test/Microsoft.Azure.Devices.Edge.Test/Metrics.cs b/test/Microsoft.Azure.Devices.Edge.Test/Metrics.cs index a6f8a03d138..be60ddce7aa 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/Metrics.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/Metrics.cs @@ -53,12 +53,15 @@ await this.runtime.DeployConfigurationAsync( { builder.AddTemporaryModule(); builder.AddMetricsValidatorConfig(metricsValidatorImage); - }, token, + }, + this.cli, + token, Context.Current.NestedEdge); // Next remove the temporary image from the deployment await this.runtime.DeployConfigurationAsync( builder => { builder.AddMetricsValidatorConfig(metricsValidatorImage); }, + this.cli, token, Context.Current.NestedEdge); } diff --git a/test/Microsoft.Azure.Devices.Edge.Test/Module.cs b/test/Microsoft.Azure.Devices.Edge.Test/Module.cs index 208a8cd789d..7efef1ab2f4 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/Module.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/Module.cs @@ -29,11 +29,12 @@ public async Task CertRenew(Protocol protocol) builder.GetModule(ModuleName.EdgeHub).WithEnvironment(("ServerCertificateRenewAfterInMs", "6000")); builder.GetModule(ModuleName.EdgeHub).WithEnvironment(new[] { ("UpstreamProtocol", protocol.ToString()) }); }, + this.cli, token, Context.Current.NestedEdge); EdgeModule edgeHub = deployment.Modules[ModuleName.EdgeHub]; - await edgeHub.WaitForStatusAsync(EdgeModuleStatus.Running, token); + await edgeHub.WaitForStatusAsync(EdgeModuleStatus.Running, this.cli, token); EdgeModule edgeAgent = deployment.Modules[ModuleName.EdgeAgent]; // certificate renew should stop edgeHub and then it should be started by edgeAgent await edgeAgent.WaitForReportedPropertyUpdatesAsync( @@ -76,6 +77,7 @@ public async Task TempSensor() builder.AddModule(SensorName, sensorImage) .WithEnvironment(new[] { ("MessageCount", "-1") }); }, + this.cli, token, Context.Current.NestedEdge); sensor = deployment.Modules[SensorName]; @@ -120,6 +122,7 @@ public async Task TempFilter() } }); }, + this.cli, token, Context.Current.NestedEdge); @@ -160,6 +163,7 @@ public async Task TempFilterFunc() } }); }, + this.cli, token, Context.Current.NestedEdge); @@ -194,6 +198,7 @@ public async Task ModuleToModuleDirectMethod( builder.AddModule(methodReceiver, receiverImage) .WithEnvironment(new[] { ("ClientTransportType", clientTransport) }); }, + this.cli, token, Context.Current.NestedEdge); diff --git a/test/Microsoft.Azure.Devices.Edge.Test/PlugAndPlay.cs b/test/Microsoft.Azure.Devices.Edge.Test/PlugAndPlay.cs index 91589785bcd..45eaf14d4f3 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/PlugAndPlay.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/PlugAndPlay.cs @@ -30,6 +30,7 @@ public async Task PlugAndPlayDeviceClient(Protocol protocol) Action config = this.BuildAddEdgeHubConfig(protocol); EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync( config, + this.cli, token, Context.Current.NestedEdge); @@ -40,6 +41,7 @@ public async Task PlugAndPlayDeviceClient(Protocol protocol) Option.Some(this.runtime.DeviceId), false, this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, Context.Current.Hostname.GetOrElse(Dns.GetHostName().ToLower()), token, @@ -73,6 +75,7 @@ public async Task PlugAndPlayModuleClient(Protocol protocol) Action config = this.BuildAddEdgeHubConfig(protocol) + this.BuildAddLoadGenConfig(protocol, loadGenImage); EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync( config, + this.cli, token, Context.Current.NestedEdge); diff --git a/test/Microsoft.Azure.Devices.Edge.Test/PriorityQueues.cs b/test/Microsoft.Azure.Devices.Edge.Test/PriorityQueues.cs index a06de9ca865..c6f74e7fde0 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/PriorityQueues.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/PriorityQueues.cs @@ -45,10 +45,10 @@ public async Task PriorityQueueModuleToModuleMessages() Action addLoadGenConfig = this.BuildAddLoadGenConfig(trackingId, loadGenImage, testInfo, false); Action addTrcConfig = TestResultCoordinatorUtil.BuildAddTestResultCoordinatorConfig(trackingId, trcImage, LoadGenModuleName, RelayerModuleName, false); - EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig, token, Context.Current.NestedEdge); + EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig, this.cli, token, Context.Current.NestedEdge); PriorityQueueTestStatus loadGenTestStatus = await this.PollUntilFinishedAsync(LoadGenModuleName, token); Action addRelayerConfig = this.BuildAddRelayerConfig(relayerImage, loadGenTestStatus); - deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig + addRelayerConfig, token, Context.Current.NestedEdge); + deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig + addRelayerConfig, this.cli, token, Context.Current.NestedEdge); await this.PollUntilFinishedAsync(RelayerModuleName, token); Assert.True(await TestResultCoordinatorUtil.IsResultValidAsync()); } @@ -71,7 +71,7 @@ public async Task PriorityQueueModuleToHubMessages() Action addTrcConfig = TestResultCoordinatorUtil.BuildAddTestResultCoordinatorConfig(trackingId, trcImage, LoadGenModuleName, "hubtest", false); Action addNetworkControllerConfig = TestResultCoordinatorUtil.BuildAddNetworkControllerConfig(trackingId, networkControllerImage); - EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig + addNetworkControllerConfig, token, Context.Current.NestedEdge); + EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig + addNetworkControllerConfig, this.cli, token, Context.Current.NestedEdge); bool networkOn = true; await this.ToggleConnectivity(!networkOn, NetworkControllerModuleName, token); await Task.Delay(TimeSpan.Parse(LoadGenTestDuration) + TimeSpan.Parse(testInfo.LoadGenStartDelay) + TimeSpan.FromSeconds(10)); @@ -101,7 +101,7 @@ public async Task PriorityQueueTimeToLive() Action addLoadGenConfig = this.BuildAddLoadGenConfig(trackingId, loadGenImage, testInfo, false); Action addTrcConfig = TestResultCoordinatorUtil.BuildAddTestResultCoordinatorConfig(trackingId, trcImage, LoadGenModuleName, RelayerModuleName, false); - EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig, token, Context.Current.NestedEdge); + EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig, this.cli, token, Context.Current.NestedEdge); PriorityQueueTestStatus loadGenTestStatus = await this.PollUntilFinishedAsync(LoadGenModuleName, token); await Profiler.Run( @@ -109,7 +109,7 @@ await Profiler.Run( "Waited for message TTL to expire"); Action addRelayerConfig = this.BuildAddRelayerConfig(relayerImage, loadGenTestStatus); - deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig + addRelayerConfig, token, Context.Current.NestedEdge); + deployment = await this.runtime.DeployConfigurationAsync(addLoadGenConfig + addTrcConfig + addRelayerConfig, this.cli, token, Context.Current.NestedEdge); await this.PollUntilFinishedAsync(RelayerModuleName, token); Assert.True(await TestResultCoordinatorUtil.IsResultValidAsync()); } diff --git a/test/Microsoft.Azure.Devices.Edge.Test/Provisioning.cs b/test/Microsoft.Azure.Devices.Edge.Test/Provisioning.cs index 7ae1d458477..b1205db2592 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/Provisioning.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/Provisioning.cs @@ -26,11 +26,11 @@ public Provisioning() Context.Current.TestRunnerProxy); } - string DeriveDeviceKey(byte[] groupKey, string registrationId) + string DeriveDeviceKey(byte[] groupKey, string deviceId) { using (var hmac = new HMACSHA256(groupKey)) { - return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(registrationId))); + return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(deviceId))); } } @@ -39,104 +39,85 @@ string DeriveDeviceKey(byte[] groupKey, string registrationId) [Category("FlakyOnArm")] public async Task DpsSymmetricKey() { - string idScope = Context.Current.DpsIdScope.Expect(() => new InvalidOperationException("Missing DPS ID scope (check dpsIdScope in context.json)")); - string groupKey = Context.Current.DpsGroupKey.Expect(() => new InvalidOperationException("Missing DPS enrollment group key (check DPS_GROUP_KEY env var)")); - string registrationId = DeviceId.Current.Generate(); + string idScope = Context.Current.DpsIdScope.Expect(() => + new InvalidOperationException("Missing DPS ID scope (check dpsIdScope in context.json)")); + string groupKey = Context.Current.DpsGroupKey.Expect(() => + new InvalidOperationException("Missing DPS enrollment group key (check DPS_GROUP_KEY env var)")); + string deviceId = DeviceId.Current.Generate(); - string deviceKey = this.DeriveDeviceKey(Convert.FromBase64String(groupKey), registrationId); + string deviceKey = this.DeriveDeviceKey(Convert.FromBase64String(groupKey), deviceId); CancellationToken token = this.TestToken; - (TestCertificates testCerts, _) = await TestCertificates.GenerateCertsAsync(registrationId, token); + (var certs, _) = await TestCertificates.GenerateEdgeCaCertsAsync( + deviceId, + this.daemon.GetCertificatesPath(), + token); await this.daemon.ConfigureAsync( async config => { - testCerts.AddCertsToConfig(config); - config.SetDpsSymmetricKey(idScope, registrationId, deviceKey); + config.SetCertificates(certs); + config.SetDpsSymmetricKey(idScope, deviceId, deviceKey); await config.UpdateAsync(token); - return ("with DPS symmetric key attestation for '{Identity}'", new object[] { registrationId }); + return ("with DPS symmetric key attestation for '{Identity}'", new object[] { deviceId }); }, token); - await this.daemon.WaitForStatusAsync(EdgeDaemonStatus.Running, token); - - var agent = new EdgeAgent(registrationId, this.iotHub); - await agent.WaitForStatusAsync(EdgeModuleStatus.Running, token); + var agent = new EdgeAgent(deviceId, this.iotHub); + await agent.WaitForStatusAsync(EdgeModuleStatus.Running, this.cli, token); await agent.PingAsync(token); Option device = await EdgeDevice.GetIdentityAsync( - registrationId, + deviceId, this.iotHub, token, takeOwnership: true); Context.Current.DeleteList.TryAdd( - registrationId, + deviceId, device.Expect(() => new InvalidOperationException( - $"Device '{registrationId}' should have been created by DPS, but was not found in '{this.iotHub.Hostname}'"))); + $"Device '{deviceId}' should have been created by DPS, but was not found in '{this.iotHub.Hostname}'"))); } [Test] [Category("FlakyOnArm")] public async Task DpsX509() { - (string, string, string) rootCa = - Context.Current.RootCaKeys.Expect(() => new InvalidOperationException("Missing DPS ID scope (check rootCaPrivateKeyPath in context.json)")); - string caCertScriptPath = - Context.Current.CaCertScriptPath.Expect(() => new InvalidOperationException("Missing CA cert script path (check caCertScriptPath in context.json)")); - string idScope = Context.Current.DpsIdScope.Expect(() => new InvalidOperationException("Missing DPS ID scope (check dpsIdScope in context.json)")); - string registrationId = DeviceId.Current.Generate(); + string idScope = Context.Current.DpsIdScope.Expect(() => + new InvalidOperationException("Missing DPS ID scope (check dpsIdScope in context.json)")); + string deviceId = DeviceId.Current.Generate(); CancellationToken token = this.TestToken; - CertificateAuthority ca = await CertificateAuthority.CreateAsync( - registrationId, - rootCa, - caCertScriptPath, - token); - - IdCertificates idCert = await ca.GenerateIdentityCertificatesAsync(registrationId, token); - (TestCertificates testCerts, _) = await TestCertificates.GenerateCertsAsync(registrationId, token); - - // Generated credentials need to be copied out of the script path because future runs - // of the script will overwrite them. - string path = Path.Combine(FixedPaths.E2E_TEST_DIR, registrationId); - string certPath = Path.Combine(path, "device_id_cert.pem"); - string keyPath = Path.Combine(path, "device_id_cert_key.pem"); - - Directory.CreateDirectory(path); - File.Copy(idCert.CertificatePath, certPath); - OsPlatform.Current.SetOwner(certPath, "aziotcs", "644"); - File.Copy(idCert.KeyPath, keyPath); - OsPlatform.Current.SetOwner(keyPath, "aziotks", "600"); + var certsPath = this.daemon.GetCertificatesPath(); + var idCerts = await TestCertificates.GenerateIdentityCertificatesAsync(deviceId, certsPath, token); + (var edgeCaCerts, _) = await TestCertificates.GenerateEdgeCaCertsAsync(deviceId, certsPath, token); await this.daemon.ConfigureAsync( async config => { - testCerts.AddCertsToConfig(config); - config.SetDpsX509(idScope, certPath, keyPath); + config.SetCertificates(edgeCaCerts); + config.SetDpsX509(idScope, idCerts.CertificatePath, idCerts.KeyPath); await config.UpdateAsync(token); - return ("with DPS X509 attestation for '{Identity}'", new object[] { registrationId }); + return ("with DPS X509 attestation for '{Identity}'", new object[] { deviceId }); }, token); - await this.daemon.WaitForStatusAsync(EdgeDaemonStatus.Running, token); - - var agent = new EdgeAgent(registrationId, this.iotHub); - await agent.WaitForStatusAsync(EdgeModuleStatus.Running, token); + var agent = new EdgeAgent(deviceId, this.iotHub); + await agent.WaitForStatusAsync(EdgeModuleStatus.Running, this.cli, token); await agent.PingAsync(token); Option device = await EdgeDevice.GetIdentityAsync( - registrationId, + deviceId, this.iotHub, token, takeOwnership: true); Context.Current.DeleteList.TryAdd( - registrationId, + deviceId, device.Expect(() => new InvalidOperationException( - $"Device '{registrationId}' should have been created by DPS, but was not found in '{this.iotHub.Hostname}'"))); + $"Device '{deviceId}' should have been created by DPS, but was not found in '{this.iotHub.Hostname}'"))); } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test/SetupFixture.cs b/test/Microsoft.Azure.Devices.Edge.Test/SetupFixture.cs index 9dc237a174a..9cc9bc2f840 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/SetupFixture.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/SetupFixture.cs @@ -10,7 +10,6 @@ namespace Microsoft.Azure.Devices.Edge.Test using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Test.Common; - using Microsoft.Azure.Devices.Edge.Test.Common.Certs; using Microsoft.Azure.Devices.Edge.Test.Helpers; using NUnit.Framework; using Serilog; @@ -37,7 +36,7 @@ public async Task BeforeAllAsync() Context.Current.LogFile.ForEach(f => loggerConfig.WriteTo.File(f)); Log.Logger = loggerConfig.CreateLogger(); - this.daemon = await OsPlatform.Current.CreateEdgeDaemonAsync(token); + this.daemon = await OsPlatform.Current.CreateEdgeDaemonAsync(Context.Current.PackagePath, token); await Profiler.Run( async () => @@ -57,15 +56,15 @@ await Profiler.Run( } } - await this.daemon.InstallAsync(Context.Current.PackagePath, Context.Current.EdgeProxy, token); + await this.daemon.InstallAsync(Context.Current.EdgeProxy, token); - // Clean the directory for test certs, keys, etc. - if (Directory.Exists(FixedPaths.E2E_TEST_DIR)) + string certsPath = this.daemon.GetCertificatesPath(); + if (Directory.Exists(certsPath)) { - Directory.Delete(FixedPaths.E2E_TEST_DIR, true); + Directory.Delete(certsPath, true); } - Directory.CreateDirectory(FixedPaths.E2E_TEST_DIR); + Directory.CreateDirectory(certsPath); await this.daemon.ConfigureAsync( async config => @@ -78,7 +77,8 @@ await this.daemon.ConfigureAsync( msgBuilder.Append("with hostname '{hostname}'"); props.Add(hostname); - string edgeAgent = Context.Current.EdgeAgentImage.GetOrElse("mcr.microsoft.com/azureiotedge-agent:1.4"); + string edgeAgent = + Context.Current.EdgeAgentImage.GetOrElse("mcr.microsoft.com/azureiotedge-agent:1.4"); Log.Verbose("Search parents"); Context.Current.ParentHostname.ForEach(parentHostname => @@ -127,10 +127,10 @@ public Task AfterAllAsync() => TryFinally.DoAsync( // Remove packages installed by this run. await this.daemon.UninstallAsync(token); - // Delete test certs, keys, etc. - if (Directory.Exists(FixedPaths.E2E_TEST_DIR)) + string certsPath = this.daemon.GetCertificatesPath(); + if (Directory.Exists(certsPath)) { - Directory.Delete(FixedPaths.E2E_TEST_DIR, true); + Directory.Delete(certsPath, true); } }, "Completed end-to-end test teardown"), @@ -139,50 +139,4 @@ public Task AfterAllAsync() => TryFinally.DoAsync( Log.CloseAndFlush(); }); } - - // Generates a test CA cert, test CA key, and trust bundle. - // TODO: Remove this once iotedge init is finished? - public class TestCertificates - { - private string deviceId; - private CaCertificates certs; - - TestCertificates(string deviceId, CaCertificates certs) - { - this.deviceId = deviceId; - this.certs = certs; - } - - public static async Task<(TestCertificates, CertificateAuthority ca)> GenerateCertsAsync(string deviceId, CancellationToken token) - { - string scriptPath = Context.Current.CaCertScriptPath.Expect( - () => new System.InvalidOperationException("Missing CA cert script path (check caCertScriptPath in context.json)")); - (string, string, string) rootCa = Context.Current.RootCaKeys.Expect( - () => new System.InvalidOperationException("Missing root CA")); - - CertificateAuthority ca = await CertificateAuthority.CreateAsync(deviceId, rootCa, scriptPath, token); - CaCertificates certs = await ca.GenerateCaCertificatesAsync(deviceId, token); - ca.EdgeCertificates = certs; - - return (new TestCertificates(deviceId, certs), ca); - } - - public void AddCertsToConfig(DaemonConfiguration config) - { - string path = Path.Combine(FixedPaths.E2E_TEST_DIR, this.deviceId); - string certPath = Path.Combine(path, "device_ca_cert.pem"); - string keyPath = Path.Combine(path, "device_ca_cert_key.pem"); - string trustBundlePath = Path.Combine(path, "trust_bundle.pem"); - - Directory.CreateDirectory(path); - File.Copy(this.certs.TrustedCertificatesPath, trustBundlePath); - OsPlatform.Current.SetOwner(trustBundlePath, "aziotcs", "644"); - File.Copy(this.certs.CertificatePath, certPath); - OsPlatform.Current.SetOwner(certPath, "aziotcs", "644"); - File.Copy(this.certs.KeyPath, keyPath); - OsPlatform.Current.SetOwner(keyPath, "aziotks", "600"); - - config.SetCertificates(new CaCertificates(certPath, keyPath, trustBundlePath)); - } - } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test/X509Device.cs b/test/Microsoft.Azure.Devices.Edge.Test/X509Device.cs index 3f87a511837..767c5cb999c 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/X509Device.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/X509Device.cs @@ -20,7 +20,7 @@ public async Task X509ManualProvision() { CancellationToken token = this.TestToken; - await this.runtime.DeployConfigurationAsync(token, Context.Current.NestedEdge); + await this.runtime.DeployConfigurationAsync(this.cli, token, Context.Current.NestedEdge); string leafDeviceId = DeviceId.Current.Generate(); @@ -31,6 +31,7 @@ public async Task X509ManualProvision() Option.Some(this.runtime.DeviceId), false, this.ca, + this.daemon.GetCertificatesPath(), this.IotHub, Context.Current.Hostname.GetOrElse(Dns.GetHostName().ToLower()), token, diff --git a/test/Microsoft.Azure.Devices.Edge.Test/helpers/BaseFixture.cs b/test/Microsoft.Azure.Devices.Edge.Test/helpers/BaseFixture.cs index 8b9475896dd..f8d945eb996 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/helpers/BaseFixture.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/helpers/BaseFixture.cs @@ -17,6 +17,7 @@ public class BaseFixture DateTime testStartTime; protected CancellationToken TestToken => this.cts.Token; + protected IotedgeCli cli; protected virtual Task BeforeTestTimerStarts() => Task.CompletedTask; protected virtual Task AfterTestTimerEnds() => Task.CompletedTask; @@ -42,14 +43,13 @@ await Profiler.Run( if ((!Context.Current.ISA95Tag) && (TestContext.CurrentContext.Result.Outcome != ResultState.Ignored)) { using var cts = new CancellationTokenSource(Context.Current.TeardownTimeout); - await NUnitLogs.CollectAsync(this.testStartTime, cts.Token); + await NUnitLogs.CollectAsync(this.testStartTime, this.cli, cts.Token); if (Context.Current.GetSupportBundle) { try { var supportBundlePath = Context.Current.LogFile.Match((file) => Path.GetDirectoryName(file), () => AppDomain.CurrentDomain.BaseDirectory); - await Process.RunAsync( - "iotedge", + await this.cli.RunAsync( $"support-bundle -o {supportBundlePath}/supportbundle-{TestContext.CurrentContext.Test.Name} --since \"{this.testStartTime:yyyy-MM-ddTHH:mm:ssZ}\"", cts.Token); } diff --git a/test/Microsoft.Azure.Devices.Edge.Test/helpers/DeviceProvisioningFixture.cs b/test/Microsoft.Azure.Devices.Edge.Test/helpers/DeviceProvisioningFixture.cs index 63556bca81d..fe35edc36da 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/helpers/DeviceProvisioningFixture.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/helpers/DeviceProvisioningFixture.cs @@ -16,7 +16,8 @@ public class DeviceProvisioningFixture : BaseFixture protected async Task BeforeAllTestsAsync() { using var cts = new CancellationTokenSource(Context.Current.SetupTimeout); - this.daemon = await OsPlatform.Current.CreateEdgeDaemonAsync(cts.Token); + this.daemon = await OsPlatform.Current.CreateEdgeDaemonAsync(Context.Current.PackagePath, cts.Token); + this.cli = this.daemon.GetCli(); } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test/helpers/ManualProvisioningFixture.cs b/test/Microsoft.Azure.Devices.Edge.Test/helpers/ManualProvisioningFixture.cs index b8c72007733..d9908af66f3 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/helpers/ManualProvisioningFixture.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/helpers/ManualProvisioningFixture.cs @@ -33,7 +33,8 @@ public ManualProvisioningFixture() protected async Task BeforeAllTestsAsync() { using var cts = new CancellationTokenSource(Context.Current.SetupTimeout); - this.daemon = await OsPlatform.Current.CreateEdgeDaemonAsync(cts.Token); + this.daemon = await OsPlatform.Current.CreateEdgeDaemonAsync(Context.Current.PackagePath, cts.Token); + this.cli = this.daemon.GetCli(); } protected async Task ConfigureDaemonAsync( @@ -46,10 +47,8 @@ protected async Task ConfigureDaemonAsync( try { - await this.daemon.WaitForStatusAsync(EdgeDaemonStatus.Running, token); - var agent = new EdgeAgent(device.Id, this.IotHub); - await agent.WaitForStatusAsync(EdgeModuleStatus.Running, token); + await agent.WaitForStatusAsync(EdgeModuleStatus.Running, this.cli, token); await agent.PingAsync(token); } @@ -60,7 +59,8 @@ protected async Task ConfigureDaemonAsync( } finally { - await NUnitLogs.CollectAsync(startTime, token); + using var cts = new CancellationTokenSource(Context.Current.TeardownTimeout); + await NUnitLogs.CollectAsync(startTime, this.cli, cts.Token); } } @@ -73,36 +73,5 @@ protected NestedEdgeConfig GetNestedEdgeConfig(IotHub iotHub) Context.Current.ParentHostname, Context.Current.Hostname); } - - public async Task SetUpCertificatesAsync(CancellationToken token, DateTime startTime, string deviceId) - { - (string, string, string) rootCa = - Context.Current.RootCaKeys.Expect(() => new InvalidOperationException("Missing DPS ID scope (check rootCaPrivateKeyPath in context.json)")); - string caCertScriptPath = - Context.Current.CaCertScriptPath.Expect(() => new InvalidOperationException("Missing CA cert script path (check caCertScriptPath in context.json)")); - string certId = Context.Current.Hostname.GetOrElse(deviceId); - - try - { - this.ca = await CertificateAuthority.CreateAsync( - certId, - rootCa, - caCertScriptPath, - token); - - CaCertificates caCert = await this.ca.GenerateCaCertificatesAsync(certId, token); - this.ca.EdgeCertificates = caCert; - } - - // ReSharper disable once RedundantCatchClause - catch - { - throw; - } - finally - { - await NUnitLogs.CollectAsync(startTime, token); - } - } } } diff --git a/test/Microsoft.Azure.Devices.Edge.Test/helpers/NUnitLogs.cs b/test/Microsoft.Azure.Devices.Edge.Test/helpers/NUnitLogs.cs index ae60d3d0c70..d72e80a244f 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/helpers/NUnitLogs.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/helpers/NUnitLogs.cs @@ -10,10 +10,10 @@ namespace Microsoft.Azure.Devices.Edge.Test.Helpers public static class NUnitLogs { - public static async Task CollectAsync(DateTime testStartTime, CancellationToken token) + public static async Task CollectAsync(DateTime testStartTime, IotedgeCli cli, CancellationToken token) { string prefix = $"{DeviceId.Current.BaseId}-{TestContext.CurrentContext.Test.NormalizedName()}"; - IEnumerable paths = await EdgeLogs.CollectAsync(testStartTime, prefix, token); + IEnumerable paths = await EdgeLogs.CollectAsync(testStartTime, prefix, cli, token); foreach (string path in paths) { TestContext.AddTestAttachment(path); diff --git a/test/Microsoft.Azure.Devices.Edge.Test/helpers/SasManualProvisioningFixture.cs b/test/Microsoft.Azure.Devices.Edge.Test/helpers/SasManualProvisioningFixture.cs index d0b61a13ee2..91d6f3dbc88 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/helpers/SasManualProvisioningFixture.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/helpers/SasManualProvisioningFixture.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Devices.Edge.Test.Helpers using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Test.Common; + using Microsoft.Azure.Devices.Edge.Test.Common.Certs; public class SasManualProvisioningFixture : ManualProvisioningFixture { @@ -42,14 +43,15 @@ protected virtual async Task SasProvisionEdgeAsync(bool withCerts = false) // This is a temporary solution see ticket: 9288683 if (!Context.Current.ISA95Tag) { - TestCertificates testCerts; - (testCerts, this.ca) = await TestCertificates.GenerateCertsAsync(this.device.Id, token); + (var certs, this.ca) = await TestCertificates.GenerateEdgeCaCertsAsync( + this.device.Id, + this.daemon.GetCertificatesPath(), + token); await this.ConfigureDaemonAsync( async config => { - testCerts.AddCertsToConfig(config); - + config.SetCertificates(certs); config.SetManualSasProvisioning( this.IotHub.Hostname, Context.Current.ParentHostname, diff --git a/test/Microsoft.Azure.Devices.Edge.Test/helpers/X509ManualProvisioningFixture.cs b/test/Microsoft.Azure.Devices.Edge.Test/helpers/X509ManualProvisioningFixture.cs index 3910e93ef0a..469d2ac6589 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/helpers/X509ManualProvisioningFixture.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/helpers/X509ManualProvisioningFixture.cs @@ -26,9 +26,18 @@ await Profiler.Run( CancellationToken token = cts.Token; DateTime startTime = DateTime.Now; string deviceId = DeviceId.Current.Generate(); + string certsPath = this.daemon.GetCertificatesPath(); - (X509Thumbprint thumbprint, string certPath, string keyPath) = await this.CreateIdentityCertAsync( - deviceId, token); + var idCerts = await TestCertificates.GenerateIdentityCertificatesAsync( + deviceId, + certsPath, + token); + var deviceCert = idCerts.Certificate; + var thumbprint = new X509Thumbprint() + { + PrimaryThumbprint = deviceCert.Thumbprint, + SecondaryThumbprint = deviceCert.Thumbprint + }; EdgeDevice device = await EdgeDevice.GetOrCreateIdentityAsync( deviceId, @@ -49,19 +58,21 @@ await Profiler.Run( Context.Current.OptimizeForPerformance, this.IotHub); - TestCertificates testCerts; - (testCerts, this.ca) = await TestCertificates.GenerateCertsAsync(device.Id, token); + (var certs, this.ca) = await TestCertificates.GenerateEdgeCaCertsAsync( + device.Id, + certsPath, + token); await this.ConfigureDaemonAsync( async config => { - testCerts.AddCertsToConfig(config); + config.SetCertificates(certs); config.SetDeviceManualX509( this.IotHub.Hostname, Context.Current.ParentHostname, device.Id, - certPath, - keyPath); + idCerts.CertificatePath, + idCerts.KeyPath); await config.UpdateAsync(token); return ("with x509 certificate for device '{Identity}'", new object[] { device.Id }); }, @@ -72,45 +83,5 @@ await this.ConfigureDaemonAsync( }, "Completed edge manual provisioning with self-signed certificate"); } - - async Task<(X509Thumbprint, string, string)> CreateIdentityCertAsync(string deviceId, CancellationToken token) - { - (string, string, string) rootCa = - Context.Current.RootCaKeys.Expect(() => new InvalidOperationException("Missing DPS ID scope (check rootCaPrivateKeyPath in context.json)")); - string caCertScriptPath = Context.Current.CaCertScriptPath.Expect( - () => new InvalidOperationException("Missing CA cert script path (check caCertScriptPath in context.json)")); - string idScope = Context.Current.DpsIdScope.Expect( - () => new InvalidOperationException("Missing DPS ID scope(check dpsIdScope in context.json)")); - - CertificateAuthority ca = await CertificateAuthority.CreateAsync( - deviceId, - rootCa, - caCertScriptPath, - token); - - var identityCerts = await ca.GenerateIdentityCertificatesAsync(deviceId, token); - - // Generated credentials need to be copied out of the script path because future runs - // of the script will overwrite them. - string path = Path.Combine(FixedPaths.E2E_TEST_DIR, deviceId); - string certPath = Path.Combine(path, "device_id_cert.pem"); - string keyPath = Path.Combine(path, "device_id_cert_key.pem"); - - Directory.CreateDirectory(path); - File.Copy(identityCerts.CertificatePath, certPath); - OsPlatform.Current.SetOwner(certPath, "aziotcs", "644"); - File.Copy(identityCerts.KeyPath, keyPath); - OsPlatform.Current.SetOwner(keyPath, "aziotks", "600"); - - X509Certificate2 deviceCert = new X509Certificate2(certPath); - - return (new X509Thumbprint() - { - PrimaryThumbprint = deviceCert.Thumbprint, - SecondaryThumbprint = deviceCert.Thumbprint - }, - certPath, - keyPath); - } } }