diff --git a/.azure-pipelines/config/credscan/credscan-suppressions.json b/.azure-pipelines/config/credscan/credscan-suppressions.json index 121de6a1199..ba8a476a77d 100644 --- a/.azure-pipelines/config/credscan/credscan-suppressions.json +++ b/.azure-pipelines/config/credscan/credscan-suppressions.json @@ -73,6 +73,15 @@ "tools\\Tests\\loadEnv.md" ], "_justification": "[ToolsTest] Examples contain random values recognized as secret" + }, + { + "file": [ + "src\\Applications\\wrapper\\v1.0\\Client\\Models\\SynchronizationSecret.cs", + "src\\Groups\\wrapper\\v1.0\\Client\\Models\\SynchronizationSecret.cs", + "src\\Identity.DirectoryManagement\\wrapper\\v1.0\\Client\\Models\\SynchronizationSecret.cs", + "src\\Users\\wrapper\\v1.0\\Client\\Models\\SynchronizationSecret.cs" + ], + "_justification": "[Wrapper] Kiota-generated model for Graph's synchronizationSecret entity enumerates the API's secret-key names (Oauth2ClientSecret and similar) as enum member strings; these are schema vocabulary from the OpenAPI document, not secret values" } ] } diff --git a/.azure-pipelines/wrapper-release.yml b/.azure-pipelines/wrapper-release.yml new file mode 100644 index 00000000000..a1b903270d4 --- /dev/null +++ b/.azure-pipelines/wrapper-release.yml @@ -0,0 +1,197 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +name: $(BuildDefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r) + +parameters: +- name: BuildAgent + default: 1es-windows-ps-compute-m + displayName: Build Agent +- name: PackageVersion + type: string + default: 3.0.0 + displayName: Package version +- name: Sign + type: boolean + default: true +- name: Publish + type: boolean + default: false +- name: InternalFeed + type: string + # The feed is PROJECT-scoped (dev.azure.com/microsoftgraph/Graph Developer Experiences/ + # _artifacts/feed/MSGraph_PowerShell_V3_Build), so publishVstsFeed needs the project + # qualifier - a bare feed name only resolves for organization-scoped feeds. + default: Graph Developer Experiences/MSGraph_PowerShell_V3_Build + displayName: Internal NuGet feed + +variables: + BuildAgent: ${{ parameters.BuildAgent }} + WrapperConfiguration: Release + WrapperPrerelease: alpha$(Build.BuildId) + +trigger: none +pr: none + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: $(BuildAgent) + settings: + networkIsolationPolicy: Permissive + sdl: + binskim: + enabled: false + justificationForDisabling: "Matches sdk-release.yml; BinSkim currently blocks internal-feed publishing." + credscan: + suppressionsFile: $(Build.SourcesDirectory)/.azure-pipelines/config/credscan/credscan-suppressions.json + policheck: + exclusionFile: $(Build.SourcesDirectory)/.azure-pipelines/config/policheck/policheck-exclusions.xml + customBuildTags: + - ES365AIMigrationTooling + stages: + - stage: Build + displayName: Build wrapper modules + jobs: + - job: Wrapper_Build + displayName: Generate, build, pack, and sign wrapper modules + timeoutInMinutes: 840 + templateContext: + outputs: + - output: pipelineArtifact + displayName: Publish wrapper module artifacts + targetPath: $(Build.ArtifactStagingDirectory) + artifactName: drop + publishLocation: Container + steps: + - script: git submodule update --init --recursive + displayName: Initialize submodules + + # Deliberately NOT install-tools.yml: that template is the AutoRest toolchain - Node, + # the private npm feed, AutoRest, Rush and a full rush rebuild - none of which this + # pipeline uses. The wrapper modules compile from committed sources, so the whole + # toolchain is the .NET SDK, feed auth for restore, and the kiota CLI. + - task: UseDotNet@2 + displayName: Use .NET SDK 10 + retryCountOnTaskFailure: 2 + inputs: + version: 10.x + - task: NuGetAuthenticate@1 + # Under 1ES network isolation api.nuget.org is not reliably reachable, so every restore + # on this pipeline - the kiota tool below and the module build after it - resolves + # through the team's own feed first, whose upstream proxies nuget.org. The config is + # written at the sources root so dotnet picks it up everywhere; NuGetAuthenticate above + # supplies the credentials. + - task: PowerShell@2 + displayName: Route NuGet through the internal feed + inputs: + targetType: inline + pwsh: true + script: | + $cfg = @' + + + + + + + + '@ + Set-Content -Path '$(Build.SourcesDirectory)/nuget.config' -Value $cfg -Encoding utf8 + Write-Host "wrote $(Build.SourcesDirectory)/nuget.config" + # Build-WrapperModule.ps1 refuses to run without kiota on PATH even under -SkipKiota + # (the guard is unconditional), and future -Generate runs need it anyway. + - task: PowerShell@2 + displayName: Install kiota CLI + retryCountOnTaskFailure: 2 + inputs: + targetType: inline + pwsh: true + workingDirectory: $(Build.SourcesDirectory) + script: | + dotnet tool install --global Microsoft.OpenApi.Kiota --configfile '$(Build.SourcesDirectory)/nuget.config' --ignore-failed-sources + if ($LASTEXITCODE -ne 0) { throw "kiota install failed with exit code $LASTEXITCODE" } + Write-Host "##vso[task.prependpath]$env:USERPROFILE\.dotnet\tools" + - template: .azure-pipelines/common-templates/security-pre-checks.yml@self + + # Version and prerelease go to the script directly (-ModuleVersion/-Prerelease); the + # script owns package identity. Nothing here edits ModuleMetadata.json - that file + # belongs to the v2 release train. + - task: PowerShell@2 + displayName: Build and pack wrapper modules + inputs: + targetType: inline + pwsh: true + script: | + $params = @{ + ApiVersion = 'v1.0' + Configuration = '$(WrapperConfiguration)' + ArtifactsLocation = '$(Build.ArtifactStagingDirectory)' + ModuleVersion = '${{ parameters.PackageVersion }}' + Prerelease = '$(WrapperPrerelease)' + # Deliberately NOT -SkipKiota: the pipeline runs the whole chain - kiota client + # generation from the committed OpenAPI docs, wrapper generation on top, then + # compile - so a change in any step is built and tested end to end, and the run + # can never fail on committed clients lagging the docs. Clients are still + # committed to the repo for reviewable diffs and clean local checkouts. + Pack = $true + } + & '$(Build.SourcesDirectory)/tools/Build-WrapperModule.ps1' @params + if ($LASTEXITCODE -ne 0) { throw "Wrapper build failed with exit code $LASTEXITCODE." } + + - task: PowerShell@2 + displayName: Verify wrapper packages were produced + inputs: + targetType: inline + pwsh: true + script: | + $packages = @(Get-ChildItem '$(Build.ArtifactStagingDirectory)' -Recurse -Filter 'Microsoft.Graph.Wrapper.*.nupkg') + Write-Host "Wrapper packages produced: $($packages.Count)" + $packages | ForEach-Object { Write-Host " $($_.FullName)" } + if ($packages.Count -eq 0) { throw 'No wrapper packages were produced.' } + + - template: .azure-pipelines/common-templates/guardian-analyzer.yml@self + + - ${{ if eq(parameters.Sign, true) }}: + - template: .azure-pipelines/common-templates/esrp/codesign-nuget.yml@self + parameters: + FolderPath: $(Build.ArtifactStagingDirectory) + Pattern: Microsoft.Graph.Wrapper.*.nupkg + + - template: .azure-pipelines/common-templates/security-post-checks.yml@self + + - ${{ if eq(parameters.Publish, true) }}: + - stage: Deploy_to_Internal_Feed + displayName: Deploy wrapper packages to internal feed + dependsOn: Build + jobs: + - deployment: DeployToInternalFeed + displayName: Publish to MSGraph PowerShell V3 build feed + environment: PowerShellInternalFeed + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + artifactName: drop + targetPath: $(System.DefaultWorkingDirectory)/drop + strategy: + runOnce: + deploy: + steps: + - task: 1ES.PublishNuget@1 + displayName: Publish wrapper packages to internal feed + inputs: + useDotNetTask: false + packageParentPath: $(System.DefaultWorkingDirectory) + packagesToPush: $(System.DefaultWorkingDirectory)/**/drop/**/Microsoft.Graph.Wrapper.*.nupkg + publishVstsFeed: ${{ parameters.InternalFeed }} + nuGetFeedType: internal + allowPackageConflicts: false \ No newline at end of file diff --git a/docs/install-wrapper-modules-from-feed.md b/docs/install-wrapper-modules-from-feed.md new file mode 100644 index 00000000000..fe69d4d3bf0 --- /dev/null +++ b/docs/install-wrapper-modules-from-feed.md @@ -0,0 +1,84 @@ +# Install wrapper modules from the internal feed + +The wrapper release pipeline publishes `Microsoft.Graph.Wrapper.*` prerelease packages to the +`MSGraph_PowerShell_V3_Build` Azure Artifacts feed for team validation. + +## Prerequisites + +- Access to the Azure DevOps project and read permission on the feed. +- PowerShellGet 2.x. +- `Microsoft.Graph.Authentication` installed at the version used by the build. + +Get the feed's NuGet v2 URL from **Azure Artifacts > Connect to feed > NuGet > v2**. PowerShellGet +2.x requires the v2 endpoint when registering a `PSRepository`. + +## Register the feed + +Use an Azure DevOps personal access token with Packaging Read permission. Enter the token only at +the secure prompt; do not put it in a script, command history, or source-controlled file. + +```powershell +$secureToken = Read-Host 'Azure DevOps PAT (Packaging: Read)' -AsSecureString +$credential = [pscredential]::new('AzureDevOps', $secureToken) +$feedUrl = 'https://pkgs.dev.azure.com///_packaging/MSGraph_PowerShell_V3_Build/nuget/v2' + +Register-PSRepository ` + -Name MSGraphPowerShellV3Build ` + -SourceLocation $feedUrl ` + -PublishLocation $feedUrl ` + -InstallationPolicy Trusted ` + -Credential $credential +``` + +Replace `/` with the values from **Connect to feed**. Do not infer them from +the feed's display name. + +## Install and validate a package + +Every pipeline run publishes a version such as `3.0.0-alpha12345`. `-AllowPrerelease` is therefore +required. + +```powershell +Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force + +Install-Module ` + -Name Microsoft.Graph.Wrapper.Users ` + -Repository MSGraphPowerShellV3Build ` + -Credential $credential ` + -AllowPrerelease ` + -Scope CurrentUser ` + -Force + +Import-Module Microsoft.Graph.Wrapper.Users -Force + +$module = Get-Module Microsoft.Graph.Wrapper.Users +$commands = @(Get-Command -Module $module.Name) +if (-not $module -or $commands.Count -eq 0) { + throw 'The wrapper module did not import or export any commands.' +} + +$module | Select-Object Name, Version, Path +Write-Host "Exported commands: $($commands.Count)" +``` + +For an operation test, start a clean PowerShell process, import only the wrapper module under test, +connect with the least privileges needed, and invoke a read-only command. Loading the shipping and +wrapper workload modules together can create command-name conflicts, even though their module and +package IDs differ. + +## Find or update packages + +```powershell +Find-Module Microsoft.Graph.Wrapper.* ` + -Repository MSGraphPowerShellV3Build ` + -Credential $credential ` + -AllowPrerelease + +Update-Module Microsoft.Graph.Wrapper.Users -AllowPrerelease -Force +``` + +Remove the repository registration when testing is complete: + +```powershell +Unregister-PSRepository -Name MSGraphPowerShellV3Build +``` \ No newline at end of file