Skip to content

Commit

Permalink
ci: Use TME via OIDC in build-cli (Azure#4519)
Browse files Browse the repository at this point in the history
Now that `azd` supports OIDC in Azure Pipelines via Azure#4343, let's use
it in the `build-cli` leg of CI. Since we now use OIDC, we can also
migrate to the TME environment for the resources created during our
tests, by using the new `azd-service-connection` service connection.

In addition to the build authoring updates, I needed to make some
small changes to the integration testing framework and recording
framework to get these changes to work when running in playback mode.

I added `AZD_DEBUG_LOGIN_FORCE_SUBSCRIPTION_REFRESH` which can be used
to force `azd login` to load (and cache) subscriptions when you log
in. This refresh does not usually happen for service principal based
logins, but we want it to happen here so that the subscription list
can be served from cache instead of hitting the `/subscriptions`
endpoint of ARM to list subscriptions in each test (and record the
result). This also ensures the environment during playback is a bit
closer to the one like a developer will have when hacking on `azd`
(since they too will have run `azd login` locally thus primed their
subscription cache).

Moving to the TME user also exposed an issue with `azd` during
playback if the list of subscriptions the user has does not include
the subscription that was used for a recorded test. In this case,
`azd` could end up trying to fetch information about this
subscription, leading to test failures because this generates requests
that don't have recorded interactions. To work around this issue, I've
added a new `AZD_DEBUG_SYNTHETIC_SUBSCRIPTION` environment variable
that can be set to a subscription ID. When set, the
`SubscriptionManager` ensures the list of subscriptions returned by
`GetSubscriptions` includes an entry for this subscription. The
integration test framework arranges for this value to be set during
playback. This allows the rest of the test to work (the fact that the
principal running the test doesn't have access to the subscription
used for the recording ends up not mattering, since we serve all
requests from the recordings instead of against live Azure).

Finally, we needed to add the endpoint that is used inside Azure
DevOps to fetch the ID token during the OIDC flow to the list of URLs
that the recording infrastructure that should not be recorded.

With this change, the `azd-login` step is no longer used across any
pipelines, so we can remove it.

Fixes Azure#4341
Fixes Azure#4501
  • Loading branch information
ellismg authored Nov 13, 2024
1 parent 5b92e06 commit 6e16f37
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 43 deletions.
3 changes: 3 additions & 0 deletions cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ overrides:
- filename: docs/proxy-configuration.md
words:
- Telerik
- filename: test/recording/recording.go
words:
- oidctoken
- filename: test/functional/testdata/samples/funcapp/getting_started.md
words:
- funcignore
Expand Down
7 changes: 6 additions & 1 deletion cli/azd/cmd/auth_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,12 @@ func (la *loginAction) Run(ctx context.Context) (*actions.ActionResult, error) {
return nil, err
}

if la.flags.clientID == "" {
forceRefresh := false
if v, err := strconv.ParseBool(os.Getenv("AZD_DEBUG_LOGIN_FORCE_SUBSCRIPTION_REFRESH")); err == nil && v {
forceRefresh = true
}

if la.flags.clientID == "" || forceRefresh {
// Update the subscriptions cache for regular users (i.e. non-service-principals).
// The caching is done here to increase responsiveness of listing subscriptions in the application.
// It also allows an implicit command for the user to refresh cached subscriptions.
Expand Down
23 changes: 23 additions & 0 deletions cli/azd/pkg/account/subscriptions_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,29 @@ func (m *SubscriptionsManager) getSubscriptions(ctx context.Context) (getSubscri
}
}

// When the integration test framework runs a test in playback mode, it sets AZD_DEBUG_SYNTHETIC_SUBSCRIPTION to the
// ID of the subscription that was used when recording the test. We ensure this subscription is always present in the
// list returned by `getSubscriptions` so that end to end tests can run successfully.
if syntheticId := os.Getenv("AZD_DEBUG_SYNTHETIC_SUBSCRIPTION"); syntheticId != "" {
found := false

for _, sub := range subscriptions {
if sub.Id == syntheticId {
found = true
break
}
}

if !found {
subscriptions = append(subscriptions, Subscription{
Id: syntheticId,
Name: "AZD Synthetic Test Subscription",
TenantId: claims.TenantId,
UserAccessTenantId: claims.TenantId,
})
}
}

return getSubscriptionsResult{
subscriptions: subscriptions,
userClaims: claims,
Expand Down
6 changes: 6 additions & 0 deletions cli/azd/test/azdcli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ func NewCLI(t *testing.T, opts ...Options) *CLI {
"AZD_DEBUG_PROVISION_PROGRESS_DISABLE=true",
"PATH="+strings.Join(opt.Session.CmdProxyPaths, string(os.PathListSeparator)))
cli.Env = append(cli.Env, env...)

if opt.Session.Playback {
if subId, has := opt.Session.Variables[recording.SubscriptionIdKey]; has {
cli.Env = append(cli.Env, fmt.Sprintf("AZD_DEBUG_SYNTHETIC_SUBSCRIPTION=%s", subId))
}
}
}

// Allow a override for custom build
Expand Down
4 changes: 3 additions & 1 deletion cli/azd/test/recording/recording.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@ func Start(t *testing.T, opts ...Options) *Session {
strings.Contains(req.URL.Path, "/azure-dev")) ||
strings.Contains(req.URL.Host, "azure-dev.azureedge.net") ||
strings.Contains(req.URL.Host, "azdrelease.azureedge.net") ||
strings.Contains(req.URL.Host, "default.exp-tas.com")
strings.Contains(req.URL.Host, "default.exp-tas.com") ||
(strings.Contains(req.URL.Host, "dev.azure.com") &&
strings.Contains(req.URL.Path, "/oidctoken"))
})

proxy := &connectHandler{
Expand Down
25 changes: 18 additions & 7 deletions eng/pipelines/templates/jobs/build-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,25 @@ jobs:
- bash: dotnet nuget add source --name dotnet9 https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json
displayName: Add internal dotnet nuget feed

- template: /eng/pipelines/templates/steps/azd-login.yml
parameters:
AzdDirectory: cli/azd
- template: /eng/pipelines/templates/steps/configure-oidc-auth.yml

- pwsh: |
./azd auth login --federated-credential-provider "azure-pipelines"
./azd config set defaults.subscription $(SubscriptionId)
condition: and(succeeded(), ne(variables['Skip.LiveTest'], 'true'))
workingDirectory: cli/azd
env:
AZURESUBSCRIPTION_CLIENT_ID: $(AzureSubscriptionClientId)
AZURESUBSCRIPTION_TENANT_ID: $(AzureSubscriptionTenantId)
AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: $(AzureSubscriptionServiceConnectionId)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
AZD_DEBUG_LOGIN_FORCE_SUBSCRIPTION_REFRESH: true
displayName: AZD Login
- task: AzureCLI@2
condition: and(succeeded(), ne(variables['Skip.LiveTest'], 'true'))
inputs:
azureSubscription: azure-sdk-tests
azureSubscription: azd-service-connection
# keepAzSessionActive is required when the service connection uses Workload Identity Federation authentication scheme
# and the script will run for more than 10 minutes.
# the ci-test.ps1 script runs for a long time and the service connection `azure-sdk-tests` use Workload Identity Federation.
Expand All @@ -115,16 +126,16 @@ jobs:
# AZD live test setup variables
CI: true
AZD_TEST_CLI_VERSION: $(CLI_VERSION)
AZD_TEST_CLIENT_ID: $(arm-client-id)
AZD_TEST_TENANT_ID: $(arm-tenant-id)
AZD_TEST_CLIENT_ID: $(AzureSubscriptionClientId)
AZD_TEST_TENANT_ID: $(AzureSubscriptionTenantId)
AZD_TEST_AZURE_SUBSCRIPTION_ID: $(SubscriptionId)
AZD_TEST_AZURE_LOCATION: eastus2
AZURE_RECORD_MODE: $(AZURE_RECORD_MODE)
# AZD Live Test: Terraform authentication via `az`
ARM_USE_CLI: true
ARM_TENANT_ID: $(arm-tenant-id)
# Code Coverage: Generate junit report to publish results
GOTESTSUM_JUNITFILE: junitTestReport.xml
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

- task: PublishTestResults@2
inputs:
Expand Down
34 changes: 0 additions & 34 deletions eng/pipelines/templates/steps/azd-login.yml

This file was deleted.

0 comments on commit 6e16f37

Please sign in to comment.