diff --git a/.azure-pipelines/templates/test-docker-image-smoke.yml b/.azure-pipelines/templates/test-docker-image-smoke.yml new file mode 100644 index 00000000000..02a724de491 --- /dev/null +++ b/.azure-pipelines/templates/test-docker-image-smoke.yml @@ -0,0 +1,96 @@ +parameters: +- name: architectures + type: object +- name: downloadArtifactNameSuffix + type: string + default: '' +- name: ShouldTest + type: boolean + default: false +- name: timeoutInMinutes + type: number + default: 100 +- name: AzCliVersion + type: string + default: '' +- name: AzCliImageName + type: string + default: '' +- name: AzCliImageTag + type: string + default: '' +- name: TestAzUserId + type: string + default: '' +- name: TestAzSubscription + type: string + default: '' +- name: TestAzTenant + type: string + default: '' +- name: TestServiceConnectionId + type: string + default: '' + +jobs: +- job: TestDockerImageSmokeTest + displayName: Smoke Test - Docker + condition: and(succeeded(), eq(${{parameters.ShouldTest}}, true)) + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + strategy: + matrix: + ${{ each arch in parameters.architectures }}: + ${{ arch.name }}: + pool: ${{ arch.pool }} + downloadArtifactName: ${{ parameters.AzCliVersion }}-docker-${{ arch.value }}${{ parameters.downloadArtifactNameSuffix }} + uploadArtifactName: ${{ parameters.AzCliVersion }}-docker-${{ arch.value }}-smoke-test${{ parameters.downloadArtifactNameSuffix }} + architecture: ${{ arch.value }} + pool: + name: $(pool) + steps: + - task: DownloadPipelineArtifact@1 + displayName: 'Download Docker Image' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/docker' + artifactName: $(downloadArtifactName) + + - bash: ./scripts/ci/install_docker.sh + displayName: Install Docker + + - task: Bash@3 + displayName: 🚀 Self Test + inputs: + targetType: 'inline' + script: | + echo "== Load docker image ==" + TAR_FILE="$SYSTEM_ARTIFACTSDIRECTORY/docker/docker-azure-cli-${{ parameters.AzCliVersion }}.tar" + docker load < $TAR_FILE + + echo "== Docker Images ==" + docker images + + echo "== Run az self-test ==" + docker run ${{ parameters.AzCliImageName }}:${{ parameters.AzCliImageTag }} /bin/bash -c "time az self-test && time az --version && tdnf list --installed" + + - task: Bash@3 + displayName: 🚀🚀 Smoke Test + inputs: + targetType: 'inline' + script: | + scUrl="${SYSTEM_OIDCREQUESTURI}?api-version=7.1&serviceConnectionId=${{parameters.TestServiceConnectionId}}" + response=$(curl -s -X POST "$scUrl" -H "Authorization: Bearer $(System.AccessToken)" -H "Content-Type: application/json" -d '') + OidcToken=$(echo "$response" | jq -r '.oidcToken') + + chmod +x ./scripts/release/docker/test_az_cli_in_pipeline.sh + docker run -v $(System.DefaultWorkingDirectory)/scripts/release/docker:/test ${{ parameters.AzCliImageName }}:${{ parameters.AzCliImageTag }} /bin/bash -c "az login --service-principal --username ${{parameters.TestAzUserId}} --tenant ${{parameters.TestAzTenant}} --federated-token $OidcToken && az account set --subscription ${{parameters.TestAzSubscription}} && /test/test_az_cli_in_pipeline.sh" + + echo "--------------------------------------------" + + mkdir output + mv $(System.DefaultWorkingDirectory)/scripts/release/docker/azure_cli_test_output.log $(System.DefaultWorkingDirectory)/output/azure_cli_test_output.log + mv $(System.DefaultWorkingDirectory)/scripts/release/docker/azure_cli_test_result.csv $(System.DefaultWorkingDirectory)/output/azure_cli_test_result.csv + ls $(System.DefaultWorkingDirectory)/output + + - publish: $(System.DefaultWorkingDirectory)/output + artifact: $(uploadArtifactName) + displayName: Publish Test Artifact diff --git a/.azure-pipelines/templates/test-docker-image-unit.yml b/.azure-pipelines/templates/test-docker-image-unit.yml new file mode 100644 index 00000000000..26b8bd38b5b --- /dev/null +++ b/.azure-pipelines/templates/test-docker-image-unit.yml @@ -0,0 +1,66 @@ +parameters: +- name: architectures + type: object +- name: downloadArtifactNamePrefix + type: string + default: 'docker-azurelinux3.0' +- name: downloadArtifactNameSuffix + type: string + default: '' +- name: ShouldTest + type: boolean + default: false +- name: timeoutInMinutes + type: number + default: 180 +- name: AzCliVersion + type: string + default: '' +- name: AzCliImageName + type: string + default: '' +- name: AzCliImageTag + type: string + default: '' + +jobs: +- job: TestDockerImageUnitTest + displayName: Unit Test - Docker + condition: and(succeeded(), eq(${{parameters.ShouldTest}}, true)) + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + strategy: + matrix: + ${{ each arch in parameters.architectures }}: + ${{ arch.name }}: + pool: ${{ arch.pool }} + downloadArtifactName: ${{ parameters.AzCliVersion }}-docker-${{ arch.value }}${{ parameters.downloadArtifactNameSuffix }} + pool: + name: $(pool) + steps: + - task: DownloadPipelineArtifact@1 + displayName: 'Download Docker Image' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/docker' + artifactName: $(downloadArtifactName) + + - bash: ./scripts/ci/install_docker.sh + displayName: Install Docker + + - bash: | + set -exv + + echo "== Load docker image ==" + TAR_FILE="$SYSTEM_ARTIFACTSDIRECTORY/docker/docker-azure-cli-${{ parameters.AzCliVersion }}.tar" + docker load < $TAR_FILE + + echo "== Run az self-test ==" + docker run ${{ parameters.AzCliImageName }}:${{ parameters.AzCliImageTag }} /bin/bash -c "time az self-test && time az --version && tdnf list --installed" + displayName: '🚀 Self Test' + + - bash: | + echo "== List docker image ==" + docker images + + echo "== Run unit test ==" + docker run --rm -v $(pwd):/azure-cli ${{ parameters.AzCliImageName }}:${{ parameters.AzCliImageTag }} /bin/bash /azure-cli/scripts/release/rpm/test_azurelinux_in_docker.sh --InstallRPM false + displayName: '🚀🚀 Unit Test' diff --git a/.azure-pipelines/test-with-copa.yml b/.azure-pipelines/test-with-copa.yml new file mode 100644 index 00000000000..1c097f33c63 --- /dev/null +++ b/.azure-pipelines/test-with-copa.yml @@ -0,0 +1,523 @@ +variables: +- template: ${{ variables.Pipeline.Workspace }}/.azure-pipelines/templates/variables.yml + +parameters: +- name: architectures + type: object + default: + - name: AMD64 + value: amd64 + pool: pool-ubuntu-latest-multi-core + - name: ARM64 + value: arm64 + pool: pool-ubuntu-latest-arm64 +- name: ShouldTest + displayName: Test? + type: boolean + default: true +- name: PatchLevel + displayName: copa-library-patch-level? + default: "patch" + values: + - patch + - minor + - major + +stages: +- stage: PrepareInfo + displayName: Prepare + jobs: + - job: PrepareAzCliVersion + displayName: Prepare Build Info + pool: + name: ${{ variables.ubuntu_pool }} + steps: + - checkout: none + - task: Bash@3 + name: PrepareAzCliVersionTask + displayName: Get Azure CLI Version + inputs: + targetType: 'inline' + script: | + curl -o __main__.py https://raw.githubusercontent.com/$BUILD_REPOSITORY_NAME/$BUILD_SOURCEBRANCH/src/azure-cli/azure/cli/__main__.py + AzCLI_VERSION=`cat __main__.py | grep __version__ | sed s/' '//g | sed s/'__version__='// | sed s/\"//g` + + AzCLI_IMAGE_NAME="clibuild_$BUILD_BUILDNUMBER" + AzCLI_IMAGE_TAG="latest" + AzCLI_IMAGE_PATCHED_TAG="latest-patched" + + echo "Azure CLI version: $AzCLI_VERSION" + echo "Azure CLI Image Name: $AzCLI_IMAGE_NAME" + echo "Azure CLI Image Tag: $AzCLI_IMAGE_TAG" + echo "Azure CLI Image Copa Patched Tag: $AzCLI_IMAGE_PATCHED_TAG" + + echo "##vso[task.setvariable variable=AzCliVersion]$AzCLI_VERSION" + + echo "##vso[task.setvariable variable=AzCliVersion;isOutput=true]$AzCLI_VERSION" + echo "##vso[task.setvariable variable=AzCliImageName;isOutput=true]$AzCLI_IMAGE_NAME" + echo "##vso[task.setvariable variable=AzCliImageTag;isOutput=true]$AzCLI_IMAGE_TAG" + echo "##vso[task.setvariable variable=AzCliImagePatchedTag;isOutput=true]$AzCLI_IMAGE_PATCHED_TAG" + + - task: PowerShell@2 + displayName: Set Build name + inputs: + targetType: inline + pwsh: true + script: | + # build name has 2 limitations + # - The maximum length of a build name is 255 characters. + # - Characters which are not allowed include '"', '/', ':', '<', '>', '\', '|', '?', '@', and '*'. + + $originalBuildName = "$(Build.BuildNumber)" + $newBuildName = "$originalBuildName - Azure CLI Docker Build $(AzCliVersion)" + + if("${{parameters.ShouldTest}}" -eq "true") { + $newBuildName = "$newBuildName with Test" + } else { + $newBuildName = "$newBuildName without Test" + } + + $notAllowedChars = @('"', '/', ':', '<', '>', '\', '|', '?', '@', '*') + $notAllowedChars | ForEach-Object {$newBuildName = $newBuildName.Replace($_, '_')} + if ( $newBuildName.Length -gt 200 ) { + $newBuildName = $newBuildName.Substring(0, 200) + } + Write-Host "##vso[build.updatebuildnumber]$newBuildName" + + - task: AzureCLI@2 + name: PrepareServiceConnectionInfoTask + displayName: Get Test Azure Service Connection Info + inputs: + azureSubscription: '$(TestAzureSubscription)' + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + # Get user, subscription and tenant info + AzUserId=$(az account show --query user.name -o tsv) + AzSubscription=$(az account show --query id -o tsv) + AzTenant=$(az account show --query tenantId -o tsv) + + echo "Azure User ID: $AzUserId" + echo "Azure Subscription: $AzSubscription" + echo "Azure Tenant: $AzTenant" + echo "Azure Service Connection Id: $AZURESUBSCRIPTION_SERVICE_CONNECTION_ID" + + # Set pipeline variables as secrets + echo "##vso[task.setvariable variable=AzUserId;issecret=true;isOutput=true]$AzUserId" + echo "##vso[task.setvariable variable=AzSubscription;issecret=true;isOutput=true]$AzSubscription" + echo "##vso[task.setvariable variable=AzTenant;issecret=true;isOutput=true]$AzTenant" + echo "##vso[task.setvariable variable=ServiceConnectionId;isOutput=true]$AZURESUBSCRIPTION_SERVICE_CONNECTION_ID" + +- stage: Build + displayName: Build + dependsOn: + - PrepareInfo + variables: + AzCliVersion: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliVersion'] ] + AzCliImageName: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImageName'] ] + AzCliImageTag: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImageTag'] ] + jobs: + - job: BuildRpmPackagesAzureLinux + displayName: Build Rpm + strategy: + matrix: + ${{ each arch in parameters.architectures }}: + ${{ arch.name }}: + image: $(BaseImage) + uploadArtifact: $(AzCliVersion)-rpm-${{ arch.value }} + pool: ${{ arch.pool }} + pool: + name: $(pool) + steps: + - bash: ./scripts/ci/install_docker.sh + displayName: Install Docker + + # - task: PipAuthenticate@1 + # displayName: 'Pip Authenticate' + # inputs: + # artifactFeeds: $(AZURE_ARTIFACTS_FEEDS) + + - task: Bash@3 + displayName: 'Build Rpm Package: Azure Linux' + inputs: + targetType: 'filePath' + filePath: scripts/release/rpm/pipeline_azurelinux.sh + + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'Generate SBOM' + inputs: + BuildDropPath: $(Build.ArtifactStagingDirectory) + + - task: PublishPipelineArtifact@0 + displayName: 'Publish RPM Artifact' + inputs: + TargetPath: $(Build.ArtifactStagingDirectory) + ArtifactName: $(uploadArtifact) + + - job: BuildDockerImageAzureLinux + displayName: Build Docker + dependsOn: + - BuildRpmPackagesAzureLinux + strategy: + matrix: + ${{ each arch in parameters.architectures }}: + ${{ arch.name }}: + pool: ${{ arch.pool }} + downloadArtifactName: $(AzCliVersion)-rpm-${{ arch.value }} + uploadArtifactName: $(AzCliVersion)-docker-${{ arch.value }} + dockerfile: azure-linux.dockerfile + image: $(BaseImage) + pool: + name: $(pool) + steps: + - bash: ./scripts/ci/install_docker.sh + displayName: Install Docker + + - task: DownloadPipelineArtifact@1 + displayName: 'Download RPM Package' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/rpm' + artifactName: $(downloadArtifactName) + + - bash: | + set -ex + mkdir docker-temp + mv $(Build.ArtifactStagingDirectory)/rpm/*.rpm ./docker-temp/azure-cli.rpm + + docker build --no-cache \ + --build-arg IMAGE=$IMAGE \ + --tag $(AzCliImageName):$(AzCliImageTag) \ + --file $DOCKERFILE \ + $BUILD_SOURCESDIRECTORY + + mkdir $(Build.ArtifactStagingDirectory)/docker + docker save -o "$(Build.ArtifactStagingDirectory)/docker/docker-azure-cli-$(AzCliVersion).tar" $(AzCliImageName):$(AzCliImageTag) + displayName: 'Build Docker Image' + + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'Generate SBOM' + inputs: + BuildDropPath: $(Build.ArtifactStagingDirectory)/docker + DockerImagesToScan: $(AzCliDockerImageTag) + + - task: PublishPipelineArtifact@0 + displayName: Publish docker Artifact + inputs: + TargetPath: $(Build.ArtifactStagingDirectory)/docker + ArtifactName: $(uploadArtifactName) + +- stage: Test1 + displayName: 1st Test - Original Image + dependsOn: + - PrepareInfo + - Build + variables: + AzCliVersion: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliVersion'] ] + AzCliImageName: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImageName'] ] + AzCliImageTag: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImageTag'] ] + TestAzUserId: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareServiceConnectionInfoTask.AzUserId'] ] + TestAzSubscription: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareServiceConnectionInfoTask.AzSubscription'] ] + TestAzTenant: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareServiceConnectionInfoTask.AzTenant'] ] + TestServiceConnectionId: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareServiceConnectionInfoTask.ServiceConnectionId'] ] + jobs: + - job: TestRpmPackagesAzureLinux + displayName: Test - Rpm + timeoutInMinutes: 180 + condition: and(succeeded(), eq(${{parameters.ShouldTest}}, true)) + pool: + name: $(pool) + strategy: + matrix: + ${{ each arch in parameters.architectures }}: + ${{ arch.name }}: + image: $(BaseImage) + downloadArtifactName: $(AzCliVersion)-rpm-${{ arch.value }} + pool: ${{ arch.pool }} + steps: + - task: DownloadPipelineArtifact@1 + displayName: 'Download RPM Artifacts' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/rpm' + artifactName: $(downloadArtifactName) + + - bash: ./scripts/ci/install_docker.sh + displayName: Install Docker + + - bash: | + set -ex + RPM_NAME=$(find $SYSTEM_ARTIFACTSDIRECTORY/rpm/ -type f -name "azure-cli-$(AzCliVersion)-1.*.rpm" -printf '%f\n') + + echo "== Test rpm package on ${IMAGE} ==" + docker pull $IMAGE + docker run --rm -e RPM_NAME=$RPM_NAME -v $SYSTEM_ARTIFACTSDIRECTORY/rpm:/mnt/rpm -v $(pwd):/azure-cli $IMAGE /bin/bash /azure-cli/scripts/release/rpm/test_azurelinux_in_docker.sh + displayName: 'Test Rpm Package' + + - template: templates/test-docker-image-smoke.yml + parameters: + architectures: ${{ parameters.architectures }} + ShouldTest: ${{ parameters.ShouldTest }} + timeoutInMinutes: 100 + AzCliVersion: $(AzCliVersion) + AzCliImageName: $(AzCliImageName) + AzCliImageTag: $(AzCliImageTag) + TestAzUserId: $(TestAzUserId) + TestAzSubscription: $(TestAzSubscription) + TestAzTenant: $(TestAzTenant) + TestServiceConnectionId: $(TestServiceConnectionId) + + - template: templates/test-docker-image-unit.yml + parameters: + architectures: ${{ parameters.architectures }} + ShouldTest: ${{ parameters.ShouldTest }} + timeoutInMinutes: 180 + AzCliVersion: $(AzCliVersion) + AzCliImageName: $(AzCliImageName) + AzCliImageTag: $(AzCliImageTag) + +- stage: PatchWithCopa + displayName: Patch with Copa + dependsOn: + - PrepareInfo + - Build + variables: + AzCliVersion: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliVersion'] ] + AzCliImageName: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImageName'] ] + AzCliImageTag: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImageTag'] ] + AzCliImagePatchedTag: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImagePatchedTag'] ] + jobs: + - job: BuildCopaPatchedDockerImage + displayName: Patch with Copa + timeoutInMinutes: 180 + strategy: + matrix: + ${{ each arch in parameters.architectures }}: + ${{ arch.name }}: + pool: ${{ arch.pool }} + downloadArtifactName: $(AzCliVersion)-docker-${{ arch.value }} + uploadArtifactName: $(AzCliVersion)-docker-${{ arch.value }}-patched + architecture: ${{ arch.value }} + pool: + name: $(pool) + + steps: + - task: DownloadPipelineArtifact@1 + displayName: 'Download Docker Image' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/docker' + artifactName: $(downloadArtifactName) + + - bash: ./scripts/ci/install_docker.sh + displayName: Install Docker + + - task: Bash@3 + displayName: Configure Docker Daemon + inputs: + targetType: 'inline' + script: | + if [ -f /etc/docker/daemon.json ]; then + echo "Docker daemon.json exists, printing content:" + cat /etc/docker/daemon.json + else + echo "Docker daemon.json does not exist, creating it..." + sudo mkdir -p /etc/docker + echo '{"features": { "containerd-snapshotter": true }}' | sudo tee /etc/docker/daemon.json + echo "Created /etc/docker/daemon.json with containerd-snapshotter enabled" + fi + sudo systemctl restart docker + + - task: Bash@3 + displayName: Install Trivy + inputs: + targetType: 'inline' + script: | + set -eux + curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/bin + echo "----------------------------------" + trivy --version + + - task: Bash@3 + displayName: Install Copa + inputs: + targetType: 'inline' + script: | + CopaDownloadUrl="https://github.com/project-copacetic/copacetic/releases/download/v$(CopaVersion)/copa_$(CopaVersion)_linux_amd64.tar.gz" + if [ "$(architecture)" = "arm64" ]; then + CopaDownloadUrl="https://github.com/project-copacetic/copacetic/releases/download/v$(CopaVersion)/copa_$(CopaVersion)_linux_arm64.tar.gz" + fi + echo "Download copa from: $CopaDownloadUrl" + curl -L -o /tmp/copa.tar.gz $CopaDownloadUrl + sudo mkdir -p /opt/copacetic + sudo tar zxf /tmp/copa.tar.gz -C /opt/copacetic + sudo chmod +x /opt/copacetic/copa + sudo ln -s /opt/copacetic/copa /usr/bin/copa + + echo "---------------- copa --version ------------------" + copa --version + echo "---------------- copa --help ------------------" + copa --help + echo "---------------- copa patch --help ------------------" + copa patch --help + echo "-----------------------------------------------------" + + - task: Bash@3 + displayName: Check Docker Image + inputs: + targetType: 'inline' + script: | + TAR_FILE="$SYSTEM_ARTIFACTSDIRECTORY/docker/docker-azure-cli-$(AzCliVersion).tar" + docker load < $TAR_FILE + + echo "== Check Docker Images ==" + docker images + + echo "== docker container ==" + docker ps + + echo "== Run az self-test ==" + docker run $(AzCliImageName):$(AzCliImageTag) /bin/bash -c "time az self-test && time az --version && tdnf list --installed" + + - task: Bash@3 + displayName: Scan with Trivy + inputs: + targetType: 'inline' + script: | + set -eux + mkdir output + export IMAGE=$(AzCliImageName):$(AzCliImageTag) + OutputFile="trivy-scan-before.json" + trivy image --pkg-types os,library --ignore-unfixed -f json -o $OutputFile $IMAGE + cp $OutputFile ./output/ + + - task: Bash@3 + displayName: Patch with Copa + name: PatchWithCopaTask + inputs: + targetType: 'inline' + script: | + export COPA_EXPERIMENTAL=1 + export IMAGE=$(AzCliImageName):$(AzCliImageTag) + copa patch -i $IMAGE -r trivy-scan-before.json --pkg-types os,library --library-patch-level ${{parameters.PatchLevel}} --tag $(AzCliImagePatchedTag) 2>&1 | tee copa-patch.log + + echo "------------- copa patched result: -------------" + cat copa-patch.log + echo "------------------------------------------------" + + PatchableVulnerabilitiesFound=true + if grep -q "Error" copa-patch.log; then + if grep -q "Error: no patchable vulnerabilities found" copa-patch.log; then + echo "no patchable vulnerabilities found" + PatchableVulnerabilitiesFound=false + else + echo "Found error in copa-patch.log:" + grep "Error" copa-patch.log + exit 1 + fi + else + PATCHED_IMAGE="$(AzCliImageName):$(AzCliImagePatchedTag)" + if ! docker image ls --format "table {{.Repository}}:{{.Tag}}" | grep -q "^$PATCHED_IMAGE$"; then + echo "Error: Patched image $PATCHED_IMAGE does not exist" + exit 1 + fi + fi + echo "##vso[task.setvariable variable=PatchableVulnerabilitiesFound]$PatchableVulnerabilitiesFound" + echo "##vso[task.setvariable variable=PatchableVulnerabilitiesFound;isOutput=true]$PatchableVulnerabilitiesFound" + + echo "== Check Docker Images ==" + docker images + + - task: Bash@3 + displayName: Check Patched Docker Image + condition: and(succeeded(), eq(variables['PatchableVulnerabilitiesFound'], 'true')) + inputs: + targetType: 'inline' + script: | + echo "== Check Docker Images ==" + docker images + + echo "== Run az self-test ==" + docker run $(AzCliImageName):$(AzCliImagePatchedTag) /bin/bash -c "time az self-test && time az --version && tdnf list --installed" + + - task: Bash@3 + displayName: Save Patched Docker Image + condition: and(succeeded(), eq(variables['PatchableVulnerabilitiesFound'], 'true')) + inputs: + targetType: 'inline' + script: | + echo "== Check Docker Images ==" + docker images + + echo "== Save Docker Image to tar file ==" + mkdir "$BUILD_STAGINGDIRECTORY/copa" + docker save -o "$BUILD_STAGINGDIRECTORY/copa/docker-azure-cli-$(AzCliVersion).tar" $(AzCliImageName):$(AzCliImagePatchedTag) + + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'SBOM' + condition: and(succeeded(), eq(variables['PatchableVulnerabilitiesFound'], 'true')) + inputs: + BuildDropPath: $(Build.ArtifactStagingDirectory)/copa + DockerImagesToScan: $(AzCliImagePatchedTag) + + - task: PublishPipelineArtifact@0 + displayName: Publish docker Artifact + condition: and(succeeded(), eq(variables['PatchableVulnerabilitiesFound'], 'true')) + inputs: + TargetPath: $(Build.ArtifactStagingDirectory)/copa + ArtifactName: $(uploadArtifactName) + + - task: Bash@3 + displayName: Scan with Trivy again + condition: and(succeeded(), eq(variables['PatchableVulnerabilitiesFound'], 'true')) + continueOnError: true + inputs: + targetType: 'inline' + script: | + set -eux + export IMAGE=$(AzCliImageName):$(AzCliImagePatchedTag) + OutputFile="trivy-scan-after.json" + trivy image --pkg-types os,library --ignore-unfixed -f json -o $OutputFile $IMAGE + cp $OutputFile ./output/ + + - publish: $(System.DefaultWorkingDirectory)/output + artifact: $(AzCliVersion)-$(architecture)-trivy + displayName: Publish Trivy Artifact + +- stage: Test2 + displayName: 2nd Test - Copa Patched Image + dependsOn: + - PrepareInfo + - Build + - PatchWithCopa + condition: and(succeeded(), eq(dependencies.PatchWithCopa.outputs['BuildCopaPatchedDockerImage.PatchWithCopaTask.PatchableVulnerabilitiesFound'], 'true')) + variables: + AzCliVersion: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliVersion'] ] + AzCliImageName: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImageName'] ] + AzCliImageTag: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImageTag'] ] + AzCliImagePatchedTag: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareAzCliVersionTask.AzCliImagePatchedTag'] ] + TestAzUserId: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareServiceConnectionInfoTask.AzUserId'] ] + TestAzSubscription: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareServiceConnectionInfoTask.AzSubscription'] ] + TestAzTenant: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareServiceConnectionInfoTask.AzTenant'] ] + TestServiceConnectionId: $[ stageDependencies.PrepareInfo.PrepareAzCliVersion.outputs['PrepareServiceConnectionInfoTask.ServiceConnectionId'] ] + jobs: + - template: templates/test-docker-image-smoke.yml + parameters: + architectures: ${{ parameters.architectures }} + ShouldTest: ${{ parameters.ShouldTest }} + downloadArtifactNameSuffix: '-patched' + timeoutInMinutes: 100 + AzCliVersion: $(AzCliVersion) + AzCliImageName: $(AzCliImageName) + AzCliImageTag: $(AzCliImagePatchedTag) + TestAzUserId: $(TestAzUserId) + TestAzSubscription: $(TestAzSubscription) + TestAzTenant: $(TestAzTenant) + TestServiceConnectionId: $(TestServiceConnectionId) + + - template: templates/test-docker-image-unit.yml + parameters: + architectures: ${{ parameters.architectures }} + ShouldTest: ${{ parameters.ShouldTest }} + downloadArtifactNameSuffix: '-patched' + timeoutInMinutes: 180 + AzCliVersion: $(AzCliVersion) + AzCliImageName: $(AzCliImageName) + AzCliImageTag: $(AzCliImagePatchedTag) diff --git a/scripts/release/docker/az_cli_commands.sh b/scripts/release/docker/az_cli_commands.sh new file mode 100644 index 00000000000..2d039fbc3d7 --- /dev/null +++ b/scripts/release/docker/az_cli_commands.sh @@ -0,0 +1,199 @@ +# Basic Information +echo "=== Basic Information ===" +az account show +az --version +az extension list + +# Template Specs +echo "=== Template Specs ===" +az ts list + +# Accounts and Subscriptions +echo "=== Accounts and Subscriptions ===" +az account list +az account subscription list + +# Resource Groups +echo "=== Resource Groups ===" +az group list + +# Virtual Machines +echo "=== Virtual Machines ===" +az vm list +az vm image list --output json +# az vm size list --location eastus +az vmss list +az disk list +az snapshot list +az image list +az sig list +az vm availability-set list + +# Networking +echo "=== Networking ===" +az network vnet list +az network subnet list --vnet-name MyVNet --resource-group MyResourceGroup 2>/dev/null || echo 'Requires specific vnet and resource group' +az network nsg list +az network nic list +az network public-ip list +az network lb list +az network application-gateway list +az network dns zone list +az network vpn-gateway list +az network express-route list +az network route-table list +az network firewall list +az network private-endpoint list +az network private-link-service list +az network nat gateway list +az network traffic-manager profile list + +# Storage +echo "=== Storage ===" +az storage account list + +# Databases +echo "=== Databases ===" +az sql server list +az sql mi list +az mysql server list +az postgres server list +az postgres flexible-server list +az mysql flexible-server list +az mariadb server list +az cosmosdb list +az cosmosdb postgres cluster list --resource-group azure-cli-test-rg + +# Containers +echo "=== Containers ===" +az aks list +az acr list +az container list +az containerapp list +az containerapp env list +az aro list + +# App Services +echo "=== App Services ===" +az webapp list +az appservice plan list +az functionapp list + +# App Configuration +echo "=== App Configuration ===" +az appconfig list + +# Security and Identity +echo "=== Security and Identity ===" +az ad sp list --show-mine +az ad user list +az ad group list +az keyvault list +az policy assignment list +az policy definition list +az policy exemption list +az policy set-definition list +az role assignment list +az role definition list +az identity list +az security pricing list +az security contact list + +# Managed Services +echo "=== Managed Services ===" +az managedservices assignment list +az managedservices definition list + +# Monitoring +echo "=== Monitoring ===" +az monitor activity-log list --start-time 2025-09-01 --end-time 2025-09-10 2>/dev/null || echo 'Requires time range specification' +az monitor log-analytics workspace list + +# Backup +echo "=== Backup ===" +az backup vault list + +# Cognitive Services +echo "=== Cognitive Services ===" +az cognitiveservices account list + +# IoT +echo "=== IoT ===" +az iot hub list +az iot dps list +az iot central app list + +# Data Box Edge +echo "=== Data Box Edge ===" +az databoxedge device list + +# Events +echo "=== Events ===" +az eventgrid topic list +az eventgrid domain list +az eventhubs namespace list + +# Service Bus +echo "=== Service Bus ===" +az servicebus namespace list + +# Relay +echo "=== Relay ===" +az relay namespace list + +# Batch +echo "=== Batch ===" +az batch account list + +# CDN and Front Door +echo "=== CDN and Front Door ===" +az cdn profile list +az afd profile list + +# API Management +echo "=== API Management ===" +az apim list + +# Logic Apps +echo "=== Logic Apps ===" +az logic workflow list + +# Search +echo "=== Search ===" +az search service list --resource-group azure-cli-test-rg + +# HDInsight and Analytics +echo "=== HDInsight and Analytics ===" +az hdinsight list +az synapse workspace list + +# Signal R +echo "=== Signal R ===" +az signalr list + +# Locations and Availability +echo "=== Locations and Availability ===" +az account list-locations +az provider list + +# Resources +echo "=== Resources ===" +az resource list --output json + +# Tags +echo "=== Tags ===" +az tag list + +# NetApp Files +echo "=== NetApp Files ===" +az netappfiles account list + +# # Compute Fleet +# echo "=== Compute Fleet ===" +# az compute-fleet list + +# Budget and Cost +echo "=== Budget and Cost ===" +az consumption budget list 2>/dev/null || echo 'Budget command may require specific permissions' +az consumption usage list 2>/dev/null || echo 'Usage command may require specific permissions' +az billing account list \ No newline at end of file diff --git a/scripts/release/docker/test_az_cli_in_pipeline.sh b/scripts/release/docker/test_az_cli_in_pipeline.sh new file mode 100644 index 00000000000..3b4a52c86cd --- /dev/null +++ b/scripts/release/docker/test_az_cli_in_pipeline.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash + +# Set output file with timestamp +OUTPUT_FILE="/test/azure_cli_test_output.log" +OUTPUT_FILE_Result="/test/azure_cli_test_result.csv" + +# Function to run command and capture output +run_cmd() { + local cmd="$1" + + echo "$cmd" + echo "Command: $cmd" >> "$OUTPUT_FILE" + + # Create temporary files for stdout and stderr + local temp_stdout=$(mktemp) + local temp_stderr=$(mktemp) + local exit_code=0 + + # Run command and capture stdout and stderr separately + if ! eval "$cmd" > "$temp_stdout" 2> "$temp_stderr"; then + exit_code=$? + echo "ERROR: Command failed with exit code $exit_code" >&2 + fi + + # Read the outputs + local stdout_content=$(cat "$temp_stdout") + local stderr_content=$(cat "$temp_stderr") + + # Write to log files + cat "$temp_stdout" >> "$OUTPUT_FILE" + cat "$temp_stderr" >> "$OUTPUT_FILE" + + # Determine execution result + local execution_result="Success" + if [ $exit_code -ne 0 ]; then + execution_result="Fail" + fi + + # Check if output is JSON format and count items + local is_json="No" + local item_count="invalid" + + if [ -n "$stdout_content" ]; then + # Try to parse as JSON using jq if available, or python as fallback + if command -v jq >/dev/null 2>&1; then + if echo "$stdout_content" | jq . >/dev/null 2>&1; then + is_json="Yes" + # Check if it's an array or object + if echo "$stdout_content" | jq -e 'type' | grep -q "array"; then + item_count=$(echo "$stdout_content" | jq 'length') + elif echo "$stdout_content" | jq -e 'type' | grep -q "object"; then + item_count="1" + fi + fi + elif command -v python3 >/dev/null 2>&1; then + if python3 -c "import json; json.loads('''$stdout_content''')" 2>/dev/null; then + is_json="Yes" + # Check if it's an array or object + local json_type=$(python3 -c "import json; data=json.loads('''$stdout_content'''); print(type(data).__name__)" 2>/dev/null) + if [ "$json_type" = "list" ]; then + item_count=$(python3 -c "import json; print(len(json.loads('''$stdout_content''')))" 2>/dev/null) + elif [ "$json_type" = "dict" ]; then + item_count="1" + fi + fi + fi + fi + + # Extract warnings and errors from stderr + local warnings=$(echo "$stderr_content" | grep -i "warning" | tr '\n' ';' | sed 's/;$//') + local errors=$(echo "$stderr_content" | grep -i "error" | tr '\n' ';' | sed 's/;$//') + + # Escape quotes for CSV + cmd_escaped=$(echo "$cmd" | sed 's/"/""/g') + warnings_escaped=$(echo "$warnings" | sed 's/"/""/g') + errors_escaped=$(echo "$errors" | sed 's/"/""/g') + + # Write to CSV result file + echo "\"$cmd_escaped\",\"$execution_result\",\"$is_json\",\"$item_count\",\"$warnings_escaped\",\"$errors_escaped\"" >> "$OUTPUT_FILE_Result" + + # Cleanup temporary files + rm -f "$temp_stdout" "$temp_stderr" + + echo "" >> "$OUTPUT_FILE" +} + +echo "==================================================" +echo "Azure CLI Test Script - All Available List Commands" +echo "==================================================" +echo "Output will be saved to: $OUTPUT_FILE" +echo "" + +echo "==================================================" >> "$OUTPUT_FILE" +echo "Azure CLI Test Script - All Available List Commands" >> "$OUTPUT_FILE" +echo "Test started at: $(date)" >> "$OUTPUT_FILE" +echo "==================================================" >> "$OUTPUT_FILE" +echo "" >> "$OUTPUT_FILE" + +# Create CSV header for result file +echo "Command,Result,IsJSON,Count,Warnings,Errors" > "$OUTPUT_FILE_Result" + +# Execute commands from az_cli_commands.sh +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +az_commands_file="$script_dir/az_cli_commands.sh" + +if [ -f "$az_commands_file" ]; then + echo "Reading commands from: $az_commands_file" + while IFS= read -r line; do + # Skip empty lines and comments that start with # + if [[ -n "$line" && ! "$line" =~ ^[[:space:]]*# ]]; then + # Check if it's an echo command for section headers + if [[ "$line" =~ ^echo ]]; then + # Execute echo commands directly + eval "$line" + elif [[ "$line" =~ ^az ]]; then + # Execute az commands through run_cmd + run_cmd "$line" + fi + elif [[ "$line" =~ ^[[:space:]]*#[[:space:]]*echo ]]; then + # Handle commented echo commands (like # echo "=== Compute Fleet ===") + echo "$line" + fi + done < "$az_commands_file" +else + echo "Warning: Command file not found: $az_commands_file" +fi + +echo "==================================================" +echo "Azure CLI Test Completed" +echo "==================================================" +echo "Full output saved to: $OUTPUT_FILE" + +echo "==================================================" >> "$OUTPUT_FILE" +echo "Azure CLI Test Completed at: $(date)" >> "$OUTPUT_FILE" +echo "==================================================" >> "$OUTPUT_FILE" diff --git a/scripts/release/rpm/test_azurelinux_in_docker.sh b/scripts/release/rpm/test_azurelinux_in_docker.sh index 69d5221632f..84c9a1c4e21 100644 --- a/scripts/release/rpm/test_azurelinux_in_docker.sh +++ b/scripts/release/rpm/test_azurelinux_in_docker.sh @@ -3,9 +3,29 @@ # This script should be run in a Azure Linux docker container. set -exv +# Parse command line arguments +InstallRPM=true # Default value +while [[ $# -gt 0 ]]; do + case $1 in + --InstallRPM) + InstallRPM="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +unset AZ_INSTALLER + export USERNAME=azureuser -tdnf --nogpgcheck install /mnt/rpm/$RPM_NAME -y +# Install RPM package only if InstallRPM is true +if [[ "$InstallRPM" == "true" ]]; then + tdnf --nogpgcheck install /mnt/rpm/$RPM_NAME -y +fi tdnf install git gcc python3-devel python3-pip findutils ca-certificates -y