') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Link environments to credential pairs by danepowell · Pull Request #2038 · acquia/cli · GitHub
Skip to content

Link environments to credential pairs - #2038

Open
danepowell wants to merge 3 commits into
acquia:mainfrom
danepowell:auth-login-environment
Open

Link environments to credential pairs#2038
danepowell wants to merge 3 commits into
acquia:mainfrom
danepowell:auth-login-environment

Conversation

@danepowell

@danepowelldanepowell commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

I frequently work against different Cloud API environments or realms (dev/staging/prod); note that I'm talking about the environments for Cloud API itself, not Acquia Cloud environments.

Each environment requires its own credentials and I'm frequently confused switching between environments if I forget to also re-run auth:login.

At the same time, I field frequent support requests from internal users about the magic variables required to connect to non-prod Cloud APIs. We may not want to expose those fully via the UI, but I think we could still make it a little more intuitive for internal customers.

Proposed changes

  • Adds an --environment option to auth:login (e.g. --environment staging) for internal use connecting to non-production Cloud API endpoints
  • Stores the resolved Cloud API and Accounts URIs alongside each credential pair in ~/.acquia/cloud_api.conf, so the environment "sticks" after login without requiring env vars on every invocation
  • Filters the existing-credential selection list to only show credentials belonging to the requested environment

Details

URI derivation: for --environment staging, the command uses https://staging.cloud.acquia.com/api and https://staging.accounts.acquia.com/api/auth/oauth/token. For prod (the default), no URIs are stored and the SDK's built-in defaults apply — identical to the current behaviour.

Priority order: env vars (ACLI_CLOUD_API_BASE_URI, ACLI_CLOUD_API_ACCOUNTS_URI) still take precedence over stored config, so existing env-var-based workflows are unaffected.

Backward compatibility: the two new fields in the keys schema default to null, so existing cloud_api.conf files without those fields validate and behave exactly as before.

Files changed

  • src/Config/CloudDataConfig.phpcloud_api_base_uri and accounts_uri optional fields added to the keys prototype
  • src/CloudApi/CloudCredentials.phpgetBaseUri()/getAccountsUri() fall thstored URIs before returning null
  • src/Command/Auth/AuthLoginCommand.php--environment option, URI derivation, environment-scoped key filtering, URI persistence
  • tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php — staging environmen
  • tests/phpunit/src/CloudApi/CloudCredentialsTest.php — URI fallthrough and env-var priority tests

Test plan

  • composer test passes (581 tests)
  • auth:login --environment staging stores cloud_api_base_uri and accountn ~/.acquia/cloud_api.conf`
  • Subsequent commands use the staging endpoints without env vars
  • auth:login (no flag) behaves identically to before — no URIs stored, prod
  • Existing cloud_api.conf without URI fields loads and operates normally
  • ACLI_CLOUD_API_BASE_URI env var still overrides the stored value

CopilotAI lite review requested due to automatic review settings August 24, 2026 18:05
@codecov

codecovBot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.55%. Comparing base (9270dd3) to head (b643847).
⚠️ Report is 1 commits behind head on main.

Files with missing linesPatch %Lines
src/Command/Auth/AuthLoginCommand.php86.66%4 Missing ⚠️
src/CloudApi/CloudCredentials.php80.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #2038 +/- ##
============================================
+ Coverage 92.54% 92.55% +0.01% - Complexity 2022 2032 +10 
============================================
Files 126 126 Lines 7307 7338 +31 ============================================
+ Hits 6762 6792 +30 - Misses 545 546 +1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown
Contributor

Try the dev build for this PR: https://acquia-cli.s3.amazonaws.com/build/pr/2038/acli.phar

curl -OL https://acquia-cli.s3.amazonaws.com/build/pr/2038/acli.phar
chmod +x acli.phar

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for linking Cloud API credential pairs to a specific Cloud API “environment/realm” (e.g., staging), so switching credentials also switches the target API endpoints without requiring users to re-set environment variables every time.

Changes:

  • Adds an --environment option to auth:login and persists the derived Cloud API and Accounts URIs alongside the saved credential pair.
  • Filters interactive credential selection to only show keys that belong to the requested environment.
  • Extends credential/config handling and tests to support stored URIs and env-var precedence.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/Command/Auth/AuthLoginCommand.phpAdds --environment, derives URIs, scopes key selection by environment, and persists environment URIs with the credential pair.
src/CloudApi/CloudCredentials.phpUpdates URI getters to fall through to stored per-key URIs after checking env vars.
src/Config/CloudDataConfig.phpExtends the keys schema with optional cloud_api_base_uri and accounts_uri fields.
tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.phpAdds coverage for staging environment persistence and ensures prod does not persist URIs.
tests/phpunit/src/CloudApi/CloudCredentialsTest.phpAdds tests for stored URI fallback and env-var priority.
Suppressed comments (1)

src/CloudApi/CloudCredentials.php:107

  • getAccountsUri() can emit a warning when there is no active key (since getActiveKeyData() returns null and an array offset is accessed). Coalesce to an empty array before reading offsets.
 return $this->getActiveKeyData()['accounts_uri'] ?? null;

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadsrc/Command/Auth/AuthLoginCommand.php
Comment threadsrc/Command/Auth/AuthLoginCommand.php
Comment threadsrc/Command/Auth/AuthLoginCommand.php
Comment threadsrc/CloudApi/CloudCredentials.php Outdated
Comment threadsrc/Command/Auth/AuthLoginCommand.php
$this
->addOption('key', 'k', InputOption::VALUE_REQUIRED, 'Your Cloud Platform API key')
->addOption('secret', 's', InputOption::VALUE_REQUIRED, 'Your Cloud Platform API secret')
->addOption('environment', null, InputOption::VALUE_REQUIRED, 'Cloud Platform API environment', 'prod')

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to making this parameter completely hidden (if that's possible), or changing the language so there's no confusion between Cloud API environments (aka realms) and Cloud environments.

@danepowell

Copy link
Copy Markdown
CollaboratorAuthor

@anujkaushal are you open to this? If so let me know and I can clean up any issues

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@danepowell