diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c275e0cf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Shell scripts must retain LF endings on every platform. +*.sh text eol=lf + +# Treat CRLF terminators in these standalone sample projects as line endings during whitespace checks. +warmup-function/pom.xml whitespace=cr-at-eol +samples/dependency-injection-example/dagger-function/pom.xml whitespace=cr-at-eol +samples/dependency-injection-example/guice-function/pom.xml whitespace=cr-at-eol diff --git a/.gitignore b/.gitignore index b9fec809..449e37cf 100644 --- a/.gitignore +++ b/.gitignore @@ -259,6 +259,9 @@ target/ .project *.iml dependency-reduced-pom.xml +# The Maven credential provider is an opt-in for ingesting uncached packages. CI authenticates +# with MavenAuthenticate@0, and committing the extension would break anonymous restores. +.mvn/ /functions-coffee-order-demo/src/main/functions/functions-coffee-order.jar /functions-coffee-order-demo/src/main/arduino/.build/ pkg/ diff --git a/README.md b/README.md index e4c19ed6..f6724e09 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,112 @@ Please refer to [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. * Run all maven commands under the root folder of this repository +### Package feed + +All Maven packages and plugins are restored from the `upstream-public` Azure Artifacts feed +(`https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1`), which is configured +as the `central` repository in every `pom.xml` in this repository. + +The repository root also has a [`settings.xml`](settings.xml) that mirrors `central` to the same +feed. It exists because a `pom.xml` cannot cover everything: + +- Maven resolves build extensions and plugin prefixes *before* a pom's `` are honored, + so those requests would otherwise go straight to Maven Central. +- `MavenAuthenticate@0` and the credential provider key credentials off the Azure Artifacts *feed + name* (`upstream-public`), while the pom repository id must be `central` in order to override the + id Maven inherits from the Super POM. The mirror id bridges the two. + +CI installs this file to `~/.m2/settings.xml`. Locally you only need it when pulling a package or +version the feed has not cached yet, in which case pass it explicitly with `mvn -s settings.xml`. + +#### Anonymous restore (default) + +The feed allows anonymous reads, so no credentials are required to build once a package version has +been saved to the feed. External contributors and fresh clones need no setup. `mvn` just works. +Never commit credentials or a `` entry to `settings.xml` in this repository because doing so +would force authentication on everyone. + +#### Authenticating (Microsoft developers only) + +Authentication is only needed to *ingest* a package version that the feed has not cached yet. The +first restore of any new or upgraded dependency will fail anonymously with: + +> No local versions of package '...'; please provide authentication to access versions from upstream +> that have not yet been saved to your feed. + +When that happens, a Microsoft developer with access to the `azfunc/public` project must run the +restore once with credentials, which pulls the version from upstream and saves it to the feed. Every +subsequent anonymous restore then succeeds. + +The recommended way to authenticate is the `artifacts-maven-credprovider`, which acquires a token via +Entra ID so you do not have to manage a PAT. + +Run the helper script for your shell from the root of your clone. It installs the credential provider +into your local Maven repository if it is missing, then writes `.mvn/extensions.xml`. Both scripts +are idempotent, so re-running them is safe: + +```powershell +./eng/scripts/Install-MavenCredentialProvider.ps1 +``` + +```bash +./eng/scripts/install-maven-credprovider.sh +``` + +Pass `-Version` / `--version` to install a different release, and `-Force` / `--force` to reinstall or +to overwrite an `.mvn/extensions.xml` the script does not manage. + +If you would rather do it by hand, the equivalent steps are: + +1. Bootstrap the credential provider once per machine. Run this from a directory outside any Maven + project, such as your home directory. It downloads the extension from the public `AzureArtifacts` + tools feed, which needs no authentication: + + ```powershell + mvn dependency:get "-Dartifact=com.microsoft.azure:artifacts-maven-credprovider:3.2.1" "-DremoteRepositories=central::::https://pkgs.dev.azure.com/artifacts-public/PublicTools/_packaging/AzureArtifacts/maven/v1" + ``` + + Using the repository id `central` matters. Maven records the extension as having come from + `central`, which is the same id this repository's `pom.xml` files declare, so the cached copy + validates during later builds. + +2. Create `.mvn/extensions.xml` at the root of your clone: + + ```xml + + + com.microsoft.azure + artifacts-maven-credprovider + 3.2.1 + + + ``` + +`.mvn/` is deliberately listed in `.gitignore`. Do not commit it. The extension exits when it +detects a build context, and committing it would break anonymous restores for everyone else. + +If you would rather not use the credential provider, you can instead add a `` entry to your +user-level `~/.m2/settings.xml` (never to a file inside this repository), using an Azure DevOps +personal access token with Packaging read and write scope: + +```xml + + + + + central + azfunc + [PERSONAL_ACCESS_TOKEN] + + + +``` + +CI covers this automatically. The `MavenAuthenticate@0` task in the build templates authenticates the +`central` repository, so merged changes to dependency versions are ingested by the pipeline. The +credential provider is not used in pipelines. + ## IntelliJ * Import the root folder of this repository as an existing project in IntelliJ diff --git a/dockertests/app-src/BlobSdkType/pom.xml b/dockertests/app-src/BlobSdkType/pom.xml index 0468e205..b86b0cd7 100644 --- a/dockertests/app-src/BlobSdkType/pom.xml +++ b/dockertests/app-src/BlobSdkType/pom.xml @@ -24,6 +24,24 @@ functions-quickstart-java-azd-eventgrid-blob + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + com.microsoft.azure.functions @@ -99,4 +117,4 @@ - \ No newline at end of file + diff --git a/dockertests/app-src/TimezoneCheck/pom.xml b/dockertests/app-src/TimezoneCheck/pom.xml index 9abd2db0..4ea45b0b 100644 --- a/dockertests/app-src/TimezoneCheck/pom.xml +++ b/dockertests/app-src/TimezoneCheck/pom.xml @@ -22,6 +22,24 @@ timezone-check + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + com.microsoft.azure.functions diff --git a/emulatedtests/pom.xml b/emulatedtests/pom.xml index 05b0a760..d65698f5 100644 --- a/emulatedtests/pom.xml +++ b/emulatedtests/pom.xml @@ -35,11 +35,10 @@ - maven.snapshots - Maven Central Snapshot Repository - https://oss.sonatype.org/content/repositories/snapshots/ + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 - false + true true @@ -49,11 +48,10 @@ - maven.snapshots - Maven Central Snapshot Repository - https://oss.sonatype.org/content/repositories/snapshots/ + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 - false + true true diff --git a/endtoendtests/pom.xml b/endtoendtests/pom.xml index 22fce2cf..49a09a27 100644 --- a/endtoendtests/pom.xml +++ b/endtoendtests/pom.xml @@ -33,11 +33,10 @@ - maven.snapshots - Maven Central Snapshot Repository - https://oss.sonatype.org/content/repositories/snapshots/ + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 - false + true true @@ -47,11 +46,10 @@ - maven.snapshots - Maven Central Snapshot Repository - https://oss.sonatype.org/content/repositories/snapshots/ + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 - false + true true diff --git a/eng/ci/templates/jobs/build.yml b/eng/ci/templates/jobs/build.yml index 9d229516..c2ae0ad3 100644 --- a/eng/ci/templates/jobs/build.yml +++ b/eng/ci/templates/jobs/build.yml @@ -8,6 +8,17 @@ jobs: os: windows steps: + # Maven resolves plugins and extensions before a pom's repositories are honored. Install the + # mirror before MavenAuthenticate@0, which adds credentials to the same settings file. + - pwsh: | + $m2 = Join-Path $HOME '.m2' + New-Item -ItemType Directory -Path $m2 -Force | Out-Null + Copy-Item '$(Build.SourcesDirectory)/settings.xml' (Join-Path $m2 'settings.xml') -Force + displayName: 'Install Maven settings.xml' + - task: MavenAuthenticate@0 + displayName: 'Authenticate Maven to CFS' + inputs: + artifactsFeeds: upstream-public - pwsh: | Get-Command mvn displayName: 'Check Maven is installed' @@ -16,4 +27,4 @@ jobs: displayName: 'Check default java version' - pwsh: | mvn clean package - displayName: 'Build java worker' \ No newline at end of file + displayName: 'Build java worker' diff --git a/eng/ci/templates/jobs/run-docker-tests-linux.yml b/eng/ci/templates/jobs/run-docker-tests-linux.yml index e4ac90e0..44a5bd8e 100644 --- a/eng/ci/templates/jobs/run-docker-tests-linux.yml +++ b/eng/ci/templates/jobs/run-docker-tests-linux.yml @@ -25,6 +25,17 @@ jobs: javaVersion: '21' steps: + # Maven resolves plugins and extensions before a pom's repositories are honored. Install the + # mirror before MavenAuthenticate@0, which adds credentials to the same settings file. + - pwsh: | + $m2 = Join-Path $HOME '.m2' + New-Item -ItemType Directory -Path $m2 -Force | Out-Null + Copy-Item '$(Build.SourcesDirectory)/settings.xml' (Join-Path $m2 'settings.xml') -Force + displayName: 'Install Maven settings.xml' + - task: MavenAuthenticate@0 + displayName: 'Authenticate Maven to CFS' + inputs: + artifactsFeeds: upstream-public - bash: | echo "=== disk BEFORE cleanup ===" df -h / diff --git a/eng/ci/templates/jobs/run-emulated-tests-linux.yml b/eng/ci/templates/jobs/run-emulated-tests-linux.yml index 63ab4346..edb97da5 100644 --- a/eng/ci/templates/jobs/run-emulated-tests-linux.yml +++ b/eng/ci/templates/jobs/run-emulated-tests-linux.yml @@ -45,6 +45,17 @@ jobs: JAVA_VERSION_SPEC: '25' steps: + # Maven resolves plugins and extensions before a pom's repositories are honored. Install the + # mirror before MavenAuthenticate@0, which adds credentials to the same settings file. + - pwsh: | + $m2 = Join-Path $HOME '.m2' + New-Item -ItemType Directory -Path $m2 -Force | Out-Null + Copy-Item '$(Build.SourcesDirectory)/settings.xml' (Join-Path $m2 'settings.xml') -Force + displayName: 'Install Maven settings.xml' + - task: MavenAuthenticate@0 + displayName: 'Authenticate Maven to CFS' + inputs: + artifactsFeeds: upstream-public - bash: | echo "=== disk BEFORE cleanup ===" df -h / @@ -116,12 +127,11 @@ jobs: .\package-pipeline.ps1 -buildNumber $buildNumber displayName: 'Executing build script' - pwsh: | - # Download Mozilla CA certificate bundle - Invoke-WebRequest -Uri "https://curl.se/ca/cacert.pem" -OutFile "./emulatedtests/confluent_cloud_cacert.pem" + ./eng/scripts/Export-JavaCacerts.ps1 -OutputPath "./emulatedtests/confluent_cloud_cacert.pem" cd ./emulatedtests mvn clean package `-Dmaven`.javadoc`.skip=true `-Dmaven`.test`.skip `-Dorg`.slf4j`.simpleLogger`.log`.org`.apache`.maven`.cli`.transfer`.Slf4jMavenTransferListener=warn `-B Copy-Item "confluent_cloud_cacert.pem" "./target/azure-functions/azure-functions-java-emulatedtests" - displayName: 'Download CA Bundle & Package Java for Emulated Tests' + displayName: 'Export CA Bundle & Package Java for Emulated Tests' - task: npmAuthenticate@0 inputs: workingFile: '$(Build.SourcesDirectory)/.npmrc' @@ -148,4 +158,4 @@ jobs: env: AzureWebJobsStorage: "UseDevelopmentStorage=true" displayName: 'Build & Run tests' - continueOnError: false \ No newline at end of file + continueOnError: false diff --git a/eng/ci/templates/jobs/run-emulated-tests-windows.yml b/eng/ci/templates/jobs/run-emulated-tests-windows.yml index b2444567..4c8490d6 100644 --- a/eng/ci/templates/jobs/run-emulated-tests-windows.yml +++ b/eng/ci/templates/jobs/run-emulated-tests-windows.yml @@ -45,6 +45,17 @@ jobs: JAVA_VERSION_SPEC: '25' steps: + # Maven resolves plugins and extensions before a pom's repositories are honored. Install the + # mirror before MavenAuthenticate@0, which adds credentials to the same settings file. + - pwsh: | + $m2 = Join-Path $HOME '.m2' + New-Item -ItemType Directory -Path $m2 -Force | Out-Null + Copy-Item '$(Build.SourcesDirectory)/settings.xml' (Join-Path $m2 'settings.xml') -Force + displayName: 'Install Maven settings.xml' + - task: MavenAuthenticate@0 + displayName: 'Authenticate Maven to CFS' + inputs: + artifactsFeeds: upstream-public - task: NuGetToolInstaller@1 inputs: checkLatest: true @@ -97,12 +108,11 @@ jobs: .\package-pipeline.ps1 -buildNumber $buildNumber displayName: 'Executing build script' - pwsh: | - # Download Mozilla CA certificate bundle - Invoke-WebRequest -Uri "https://curl.se/ca/cacert.pem" -OutFile "./emulatedtests/confluent_cloud_cacert.pem" + ./eng/scripts/Export-JavaCacerts.ps1 -OutputPath "./emulatedtests/confluent_cloud_cacert.pem" cd ./emulatedtests mvn clean package `-Dmaven`.javadoc`.skip=true `-Dmaven`.test`.skip `-Dorg`.slf4j`.simpleLogger`.log`.org`.apache`.maven`.cli`.transfer`.Slf4jMavenTransferListener=warn `-B Copy-Item "confluent_cloud_cacert.pem" "./target/azure-functions/azure-functions-java-emulatedtests" - displayName: 'Download CA Bundle & Package Java for Emulated Tests' + displayName: 'Export CA Bundle & Package Java for Emulated Tests' - task: npmAuthenticate@0 inputs: workingFile: '$(Build.SourcesDirectory)/.npmrc' @@ -128,4 +138,4 @@ jobs: env: AzureWebJobsStorage: "UseDevelopmentStorage=true" displayName: 'Build & Run tests' - continueOnError: false \ No newline at end of file + continueOnError: false diff --git a/eng/ci/templates/official/jobs/build-artifacts.yml b/eng/ci/templates/official/jobs/build-artifacts.yml index d9c4a6b5..9afef0c2 100644 --- a/eng/ci/templates/official/jobs/build-artifacts.yml +++ b/eng/ci/templates/official/jobs/build-artifacts.yml @@ -28,6 +28,17 @@ jobs: isRelease: $[variables.isReleaseTemp] steps: + # Maven resolves plugins and extensions before a pom's repositories are honored. Install the + # mirror before MavenAuthenticate@0, which adds credentials to the same settings file. + - pwsh: | + $m2 = Join-Path $HOME '.m2' + New-Item -ItemType Directory -Path $m2 -Force | Out-Null + Copy-Item '$(Build.SourcesDirectory)/settings.xml' (Join-Path $m2 'settings.xml') -Force + displayName: 'Install Maven settings.xml' + - task: MavenAuthenticate@0 + displayName: 'Authenticate Maven to CFS' + inputs: + artifactsFeeds: upstream-public - task: NuGetToolInstaller@1 inputs: checkLatest: true @@ -57,4 +68,4 @@ jobs: Contents: '*.nupkg' TargetFolder: '$(Build.ArtifactStagingDirectory)' CleanTargetFolder: true - displayName: 'Copying files for artifacts' \ No newline at end of file + displayName: 'Copying files for artifacts' diff --git a/eng/ci/templates/official/jobs/run-e2e-tests-linux.yml b/eng/ci/templates/official/jobs/run-e2e-tests-linux.yml index 6584dcec..e6736561 100644 --- a/eng/ci/templates/official/jobs/run-e2e-tests-linux.yml +++ b/eng/ci/templates/official/jobs/run-e2e-tests-linux.yml @@ -38,6 +38,17 @@ jobs: JAVA_VERSION_SPEC: '21' steps: + # Maven resolves plugins and extensions before a pom's repositories are honored. Install the + # mirror before MavenAuthenticate@0, which adds credentials to the same settings file. + - pwsh: | + $m2 = Join-Path $HOME '.m2' + New-Item -ItemType Directory -Path $m2 -Force | Out-Null + Copy-Item '$(Build.SourcesDirectory)/settings.xml' (Join-Path $m2 'settings.xml') -Force + displayName: 'Install Maven settings.xml' + - task: MavenAuthenticate@0 + displayName: 'Authenticate Maven to CFS' + inputs: + artifactsFeeds: upstream-public - task: NuGetToolInstaller@1 inputs: checkLatest: true @@ -100,12 +111,11 @@ jobs: func --version displayName: 'Setup Core Tools - Linux' - pwsh: | - # Download Mozilla CA certificate bundle - Invoke-WebRequest -Uri "https://curl.se/ca/cacert.pem" -OutFile "./endtoendtests/confluent_cloud_cacert.pem" + ./eng/scripts/Export-JavaCacerts.ps1 -OutputPath "./endtoendtests/confluent_cloud_cacert.pem" cd ./endtoendtests mvn clean package `-Dmaven`.javadoc`.skip=true `-Dmaven`.test`.skip `-Dorg`.slf4j`.simpleLogger`.log`.org`.apache`.maven`.cli`.transfer`.Slf4jMavenTransferListener=warn `-B Copy-Item "confluent_cloud_cacert.pem" "./target/azure-functions/azure-functions-java-endtoendtests" - displayName: 'Download CA Bundle & Package Java for E2E' + displayName: 'Export CA Bundle & Package Java for E2E' - task: NuGetAuthenticate@1 displayName: 'Authenticate NuGet to CFS' - task: DotNetCoreCLI@2 @@ -137,4 +147,4 @@ jobs: ApplicationInsightAPPID: $(ApplicationInsightAPPID) ApplicationInsightAgentVersion: $(ApplicationInsightAgentVersion) displayName: 'Build & Run tests' - continueOnError: false \ No newline at end of file + continueOnError: false diff --git a/eng/ci/templates/official/jobs/run-e2e-tests-windows.yml b/eng/ci/templates/official/jobs/run-e2e-tests-windows.yml index 3862cec4..74989631 100644 --- a/eng/ci/templates/official/jobs/run-e2e-tests-windows.yml +++ b/eng/ci/templates/official/jobs/run-e2e-tests-windows.yml @@ -34,6 +34,17 @@ jobs: JDK_PATH: 'jdk-21.0.1+12' steps: + # Maven resolves plugins and extensions before a pom's repositories are honored. Install the + # mirror before MavenAuthenticate@0, which adds credentials to the same settings file. + - pwsh: | + $m2 = Join-Path $HOME '.m2' + New-Item -ItemType Directory -Path $m2 -Force | Out-Null + Copy-Item '$(Build.SourcesDirectory)/settings.xml' (Join-Path $m2 'settings.xml') -Force + displayName: 'Install Maven settings.xml' + - task: MavenAuthenticate@0 + displayName: 'Authenticate Maven to CFS' + inputs: + artifactsFeeds: upstream-public - task: NuGetToolInstaller@1 inputs: checkLatest: true @@ -85,12 +96,11 @@ jobs: func --version displayName: 'Setup Core Tools - Windows' - pwsh: | - # Download Mozilla CA certificate bundle - Invoke-WebRequest -Uri "https://curl.se/ca/cacert.pem" -OutFile "./endtoendtests/confluent_cloud_cacert.pem" + ./eng/scripts/Export-JavaCacerts.ps1 -OutputPath "./endtoendtests/confluent_cloud_cacert.pem" cd ./endtoendtests mvn clean package `-Dmaven`.javadoc`.skip=true `-Dmaven`.test`.skip `-Dorg`.slf4j`.simpleLogger`.log`.org`.apache`.maven`.cli`.transfer`.Slf4jMavenTransferListener=warn `-B Copy-Item "confluent_cloud_cacert.pem" "./target/azure-functions/azure-functions-java-endtoendtests" - displayName: 'Download CA Bundle & Package Java for E2E' + displayName: 'Export CA Bundle & Package Java for E2E' - task: NuGetAuthenticate@1 displayName: 'Authenticate NuGet to CFS' - task: DotNetCoreCLI@2 @@ -122,4 +132,4 @@ jobs: ApplicationInsightAPPID: $(ApplicationInsightAPPID) ApplicationInsightAgentVersion: $(ApplicationInsightAgentVersion) displayName: 'Build & Run tests' - continueOnError: false \ No newline at end of file + continueOnError: false diff --git a/eng/scripts/Export-JavaCacerts.ps1 b/eng/scripts/Export-JavaCacerts.ps1 new file mode 100644 index 00000000..87ade462 --- /dev/null +++ b/eng/scripts/Export-JavaCacerts.ps1 @@ -0,0 +1,51 @@ +#!/usr/bin/env pwsh + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string] $OutputPath +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$keytool = Get-Command keytool -ErrorAction Stop +$javaHome = if ($env:JAVA_HOME) { + $env:JAVA_HOME +} +else { + Split-Path (Split-Path $keytool.Source -Parent) -Parent +} + +$trustStore = @( + (Join-Path $javaHome 'lib/security/cacerts') + (Join-Path $javaHome 'jre/lib/security/cacerts') +) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1 + +if (-not $trustStore) { + throw "Could not find the Java cacerts trust store under '$javaHome'." +} + +$keytoolOutput = & $keytool.Source -list -rfc -keystore $trustStore -storepass changeit 2>&1 +if ($LASTEXITCODE -ne 0) { + throw "keytool failed with exit code $LASTEXITCODE.`n$($keytoolOutput -join [Environment]::NewLine)" +} + +$certificates = [regex]::Matches( + ($keytoolOutput -join "`n"), + '-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----', + [Text.RegularExpressions.RegexOptions]::Singleline) + +if ($certificates.Count -eq 0) { + throw "No PEM certificates were exported from '$trustStore'." +} + +$resolvedOutputPath = [IO.Path]::GetFullPath($OutputPath) +$outputDirectory = Split-Path $resolvedOutputPath -Parent +New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null +[IO.File]::WriteAllText( + $resolvedOutputPath, + (($certificates.Value -join "`n") + "`n"), + [Text.UTF8Encoding]::new($false)) + +Write-Host "Exported $($certificates.Count) Java root certificates to '$resolvedOutputPath'." \ No newline at end of file diff --git a/eng/scripts/Install-MavenCredentialProvider.ps1 b/eng/scripts/Install-MavenCredentialProvider.ps1 new file mode 100644 index 00000000..254508f9 --- /dev/null +++ b/eng/scripts/Install-MavenCredentialProvider.ps1 @@ -0,0 +1,156 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Bootstraps the Azure Artifacts Maven credential provider for local development. + +.DESCRIPTION + Maven packages for this repository are restored from an Azure Artifacts feed. Reads are + anonymous, so this script is only needed by Microsoft developers who have to ingest a package + version that the feed has not cached yet. + + The script: + 1. Verifies the credential provider is present in the local Maven repository, and downloads it + from the public AzureArtifacts tools feed if it is not. + 2. Writes '.mvn/extensions.xml' at the root of the repository so Maven loads the provider. + + '.mvn/' is intentionally listed in .gitignore. The extension exits when it detects a build + context, and committing it would force an authenticated restore on anonymous consumers. Azure + Pipelines uses the MavenAuthenticate@0 task instead. + +.PARAMETER Version + Version of the credential provider to install. Defaults to the version pinned by this script. + +.PARAMETER LocalRepositoryPath + Path to the local Maven repository. Defaults to '~/.m2/repository'. + +.PARAMETER Force + Overwrite an existing '.mvn/extensions.xml' even if it declares extensions this script does not + manage, and re-download the credential provider even when it is already installed. + +.EXAMPLE + ./eng/scripts/Install-MavenCredentialProvider.ps1 + +.LINK + https://eng.ms/docs/coreai/devdiv/one-engineering-system-1es/1es-docs/azure-artifacts/maven-credprovider +#> + +[CmdletBinding()] +param( + [string] $Version = '3.2.1', + [string] $LocalRepositoryPath, + [switch] $Force +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$groupId = 'com.microsoft.azure' +$artifactId = 'artifacts-maven-credprovider' +$bootstrapFeed = 'https://pkgs.dev.azure.com/artifacts-public/PublicTools/_packaging/AzureArtifacts/maven/v1' + +# Maven records the extension against this repository id. It must match the of the repositories +# declared in this repository's pom.xml files, otherwise resolution fails validation later. +$repositoryId = 'central' + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..' '..')).Path + +if (-not (Get-Command mvn -ErrorAction SilentlyContinue)) { + throw "Maven ('mvn') was not found on PATH. Install Apache Maven 3.0 or above and try again." +} + +if (-not $LocalRepositoryPath) { + $LocalRepositoryPath = Join-Path $HOME '.m2' 'repository' +} + +$artifactDirectory = $LocalRepositoryPath +foreach ($segment in ($groupId.Split('.') + @($artifactId, $Version))) { + $artifactDirectory = Join-Path $artifactDirectory $segment +} + +$artifactPath = Join-Path $artifactDirectory "$artifactId-$Version.jar" + +if ((Test-Path $artifactPath) -and -not $Force) { + Write-Host "Credential provider $Version is already installed at '$artifactPath'." +} +else { + Write-Host "Installing credential provider $Version from the public tools feed..." + + # The bootstrap must run outside of any Maven project so that this repository's own repository + # and extension configuration does not take part in resolving the extension itself. + $workingDirectory = Join-Path ([IO.Path]::GetTempPath()) ('credprovider-bootstrap-' + [Guid]::NewGuid().ToString('n')) + New-Item -ItemType Directory -Path $workingDirectory -Force | Out-Null + + try { + Push-Location $workingDirectory + try { + $mvnArgs = @( + '--batch-mode' + 'dependency:get' + "-Dartifact=${groupId}:${artifactId}:${Version}" + "-DremoteRepositories=${repositoryId}::::${bootstrapFeed}" + ) + + if ($PSBoundParameters.ContainsKey('LocalRepositoryPath')) { + $mvnArgs += "-Dmaven.repo.local=$LocalRepositoryPath" + } + + & mvn @mvnArgs + if ($LASTEXITCODE -ne 0) { + throw "'mvn dependency:get' failed with exit code $LASTEXITCODE." + } + } + finally { + Pop-Location + } + } + finally { + Remove-Item $workingDirectory -Recurse -Force -ErrorAction SilentlyContinue + } + + if (-not (Test-Path $artifactPath)) { + throw "Bootstrap reported success but '$artifactPath' was not found. If a mirror is configured in your settings.xml, temporarily disable it and retry." + } + + Write-Host "Installed credential provider to '$artifactPath'." +} + +$extensionsDirectory = Join-Path $repoRoot '.mvn' +$extensionsPath = Join-Path $extensionsDirectory 'extensions.xml' + +if ((Test-Path $extensionsPath) -and -not $Force) { + $existing = Get-Content $extensionsPath -Raw + + if ($existing -notmatch [regex]::Escape($artifactId)) { + throw "'$extensionsPath' already exists and declares extensions this script does not manage. Review it manually, or re-run with -Force to overwrite it." + } + + if ($existing -match "\s*$([regex]::Escape($Version))\s*") { + Write-Host "'$extensionsPath' is already configured for version $Version." + Write-Host 'Done.' + return + } +} + +$extensionsContent = @" + + + + + $groupId + $artifactId + $Version + + +"@ + +New-Item -ItemType Directory -Path $extensionsDirectory -Force | Out-Null +Set-Content -Path $extensionsPath -Value $extensionsContent -Encoding utf8 + +Write-Host "Wrote '$extensionsPath' for version $Version." +Write-Host 'Done.' diff --git a/eng/scripts/install-maven-credprovider.sh b/eng/scripts/install-maven-credprovider.sh new file mode 100644 index 00000000..00135d40 --- /dev/null +++ b/eng/scripts/install-maven-credprovider.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# +# Bootstraps the Azure Artifacts Maven credential provider for local development. +# +# Maven packages for this repository are restored from an Azure Artifacts feed. Reads are anonymous, +# so this script is only needed by Microsoft developers who have to ingest a package version that +# the feed has not cached yet. +# +# The script: +# 1. Verifies the credential provider is present in the local Maven repository, and downloads it +# from the public AzureArtifacts tools feed if it is not. +# 2. Writes '.mvn/extensions.xml' at the root of the repository so Maven loads the provider. +# +# '.mvn/' is intentionally listed in .gitignore. The extension exits when it detects a build context, +# and committing it would force an authenticated restore on anonymous consumers. Azure Pipelines +# uses the MavenAuthenticate@0 task instead. +# +# See https://eng.ms/docs/coreai/devdiv/one-engineering-system-1es/1es-docs/azure-artifacts/maven-credprovider + +set -euo pipefail + +GROUP_ID='com.microsoft.azure' +ARTIFACT_ID='artifacts-maven-credprovider' +BOOTSTRAP_FEED='https://pkgs.dev.azure.com/artifacts-public/PublicTools/_packaging/AzureArtifacts/maven/v1' + +# Maven records the extension against this repository id. It must match the of the repositories +# declared in this repository's pom.xml files, otherwise resolution fails validation later. +REPOSITORY_ID='central' + +version='3.2.1' +local_repository_path='' +force=false + +usage() { + cat <<'EOF' +Usage: install-maven-credprovider.sh [options] + +Options: + -v, --version Version of the credential provider to install. + -l, --local-repository Path to the local Maven repository. Defaults to ~/.m2/repository. + -f, --force Overwrite an unmanaged .mvn/extensions.xml and re-download the + credential provider even when it is already installed. + -h, --help Show this help text. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + -v|--version) + [[ $# -ge 2 ]] || { echo "error: $1 requires a value" >&2; exit 1; } + version="$2" + shift 2 + ;; + -l|--local-repository) + [[ $# -ge 2 ]] || { echo "error: $1 requires a value" >&2; exit 1; } + local_repository_path="$2" + shift 2 + ;; + -f|--force) + force=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "error: unknown argument '$1'" >&2 + usage >&2 + exit 1 + ;; + esac +done + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "$script_dir/../.." && pwd)" + +if ! command -v mvn >/dev/null 2>&1; then + echo "error: Maven ('mvn') was not found on PATH. Install Apache Maven 3.0 or above and try again." >&2 + exit 1 +fi + +local_repository_specified=true +if [[ -z "$local_repository_path" ]]; then + local_repository_specified=false + local_repository_path="$HOME/.m2/repository" +fi + +group_path="${GROUP_ID//./\/}" +artifact_path="$local_repository_path/$group_path/$ARTIFACT_ID/$version/$ARTIFACT_ID-$version.jar" + +if [[ -f "$artifact_path" && "$force" != true ]]; then + echo "Credential provider $version is already installed at '$artifact_path'." +else + echo "Installing credential provider $version from the public tools feed..." + + # The bootstrap must run outside of any Maven project so that this repository's own repository + # and extension configuration does not take part in resolving the extension itself. + working_directory="$(mktemp -d)" + cleanup() { rm -rf "$working_directory"; } + trap cleanup EXIT + + mvn_args=( + --batch-mode + dependency:get + "-Dartifact=${GROUP_ID}:${ARTIFACT_ID}:${version}" + "-DremoteRepositories=${REPOSITORY_ID}::::${BOOTSTRAP_FEED}" + ) + + if [[ "$local_repository_specified" == true ]]; then + mvn_args+=("-Dmaven.repo.local=$local_repository_path") + fi + + (cd "$working_directory" && mvn "${mvn_args[@]}") + + if [[ ! -f "$artifact_path" ]]; then + echo "error: bootstrap reported success but '$artifact_path' was not found." >&2 + echo "If a mirror is configured in your settings.xml, temporarily disable it and retry." >&2 + exit 1 + fi + + echo "Installed credential provider to '$artifact_path'." +fi + +extensions_directory="$repo_root/.mvn" +extensions_path="$extensions_directory/extensions.xml" + +if [[ -f "$extensions_path" && "$force" != true ]]; then + if ! grep -q "$ARTIFACT_ID" "$extensions_path"; then + echo "error: '$extensions_path' already exists and declares extensions this script does not manage." >&2 + echo "Review it manually, or re-run with --force to overwrite it." >&2 + exit 1 + fi + + if grep -qE "[[:space:]]*${version//./\\.}[[:space:]]*" "$extensions_path"; then + echo "'$extensions_path' is already configured for version $version." + echo 'Done.' + exit 0 + fi +fi + +mkdir -p "$extensions_directory" +cat >"$extensions_path" < + + + + $GROUP_ID + $ARTIFACT_ID + $version + + +EOF + +echo "Wrote '$extensions_path' for version $version." +echo 'Done.' diff --git a/pom.xml b/pom.xml index f2713957..6f10206f 100644 --- a/pom.xml +++ b/pom.xml @@ -45,17 +45,28 @@ - maven.snapshots - Maven Central Snapshot Repository - https://oss.sonatype.org/content/repositories/snapshots/ + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 - false + true true + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + + true + + + true + + + com.microsoft.azure.functions @@ -448,4 +459,4 @@ - \ No newline at end of file + diff --git a/samples/dependency-injection-example/dagger-function/pom.xml b/samples/dependency-injection-example/dagger-function/pom.xml index 0aa4f14e..426e0171 100644 --- a/samples/dependency-injection-example/dagger-function/pom.xml +++ b/samples/dependency-injection-example/dagger-function/pom.xml @@ -17,6 +17,24 @@ dagger-function-20221101192648097 + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + com.microsoft.azure.functions diff --git a/samples/dependency-injection-example/guice-function/pom.xml b/samples/dependency-injection-example/guice-function/pom.xml index de01f078..43d638d6 100644 --- a/samples/dependency-injection-example/guice-function/pom.xml +++ b/samples/dependency-injection-example/guice-function/pom.xml @@ -17,6 +17,24 @@ juice-test-20220912191553642 + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + com.microsoft.azure.functions diff --git a/samples/spring-cloud-example/pom.xml b/samples/spring-cloud-example/pom.xml index 7e56a510..19d9dfa1 100644 --- a/samples/spring-cloud-example/pom.xml +++ b/samples/spring-cloud-example/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.0.1-SNAPSHOT + 3.0.1 @@ -31,6 +31,24 @@ sample-service-plan EP1 + + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + diff --git a/settings.xml b/settings.xml new file mode 100644 index 00000000..dca7f6fd --- /dev/null +++ b/settings.xml @@ -0,0 +1,25 @@ + + + + + + upstream-public + Azure Functions public upstream feed + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + central + + + diff --git a/warmup-function/pom.xml b/warmup-function/pom.xml index 50e73ac9..2110242d 100644 --- a/warmup-function/pom.xml +++ b/warmup-function/pom.xml @@ -14,6 +14,24 @@ 1.8 + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + + + + central + https://pkgs.dev.azure.com/azfunc/public/_packaging/upstream-public/maven/v1 + true + true + + + com.microsoft.azure.functions