diff --git a/packages/spacecat-shared-cloud-manager-client/src/index.js b/packages/spacecat-shared-cloud-manager-client/src/index.js index 97107a276..63d449be5 100644 --- a/packages/spacecat-shared-cloud-manager-client/src/index.js +++ b/packages/spacecat-shared-cloud-manager-client/src/index.js @@ -261,7 +261,9 @@ export default class CloudManagerClient { * Builds authenticated git arguments for a remote command (clone, push, or pull). * * Both repo types use http.extraheader for authentication: - * - Standard repos: Basic auth header via extraheader on the repo URL + * - Standard repos: Basic auth header via extraheader scoped to the org prefix + * (scheme + host + '/' + orgName + '/'), so the header covers all repos and submodules + * belonging to that customer org without granting access to other orgs on the same host * - BYOG repos: Bearer token + API key + IMS org ID via extraheader on the CM Repo URL * * @param {string} command - The git command ('clone', 'push', or 'pull') @@ -277,8 +279,11 @@ export default class CloudManagerClient { if (repoType === CM_REPO_TYPE.STANDARD) { const credentials = this.#getStandardRepoCredentials(programId); const basicAuth = Buffer.from(credentials).toString('base64'); + const parsedUrl = new URL(repoUrl); + const orgName = parsedUrl.pathname.split('/')[1]; + const repoOrgPrefix = `${parsedUrl.origin}/${orgName}/`; return [ - '-c', `http.${repoUrl}.extraheader=Authorization: Basic ${basicAuth}`, + '-c', `http.${repoOrgPrefix}.extraheader=Authorization: Basic ${basicAuth}`, command, repoUrl, ]; } @@ -317,7 +322,7 @@ export default class CloudManagerClient { this.log.info(`Cloning CM repository: program=${programId}, repo=${repositoryId}, type=${repoType}`); const args = await this.#buildAuthGitArgs('clone', programId, repositoryId, { imsOrgId, repoType, repoUrl }); - this.#execGit([...args, clonePath]); + this.#execGit([...args, '--recurse-submodules', clonePath]); this.log.info(`Repository cloned to ${clonePath}`); this.#logTmpDiskUsage('clone'); diff --git a/packages/spacecat-shared-cloud-manager-client/test/cloud-manager-client.test.js b/packages/spacecat-shared-cloud-manager-client/test/cloud-manager-client.test.js index 96a52a824..aea348a8a 100644 --- a/packages/spacecat-shared-cloud-manager-client/test/cloud-manager-client.test.js +++ b/packages/spacecat-shared-cloud-manager-client/test/cloud-manager-client.test.js @@ -278,7 +278,7 @@ describe('CloudManagerClient', () => { const cloneArgs = getGitArgs(execFileSyncStub.firstCall); const cloneArgsStr = getGitArgsStr(execFileSyncStub.firstCall); expect(cloneArgs).to.include('clone'); - expect(cloneArgsStr).to.include(`http.${TEST_STANDARD_REPO_URL}.extraheader=Authorization: Basic c3RkdXNlcjpzdGR0b2tlbjEyMw==`); + expect(cloneArgsStr).to.include('http.https://git.cloudmanager.adobe.com/myorg/.extraheader=Authorization: Basic c3RkdXNlcjpzdGR0b2tlbjEyMw=='); expect(cloneArgsStr).to.include(TEST_STANDARD_REPO_URL); expect(cloneArgs).to.include(EXPECTED_CLONE_PATH); // No credentials in the URL itself @@ -286,6 +286,19 @@ describe('CloudManagerClient', () => { expect(cloneArgsStr).to.not.include('Bearer'); }); + it('includes --recurse-submodules in the clone arguments', async () => { + const client = CloudManagerClient.createFrom(createContext()); + + await client.clone( + TEST_PROGRAM_ID, + TEST_REPO_ID, + { imsOrgId: TEST_IMS_ORG_ID }, + ); + + const gitArgs = getGitArgs(execFileSyncStub.firstCall); + expect(gitArgs).to.include('--recurse-submodules'); + }); + it('throws when standard credentials not found for programId', async () => { const client = CloudManagerClient.createFrom( createContext({ CM_STANDARD_REPO_CREDENTIALS: TEST_STANDARD_CREDENTIALS }), @@ -784,7 +797,7 @@ describe('CloudManagerClient', () => { const pushArgs = getGitArgs(execFileSyncStub.firstCall); const pushArgStr = getGitArgsStr(execFileSyncStub.firstCall); expect(pushArgStr).to.include('push'); - expect(pushArgStr).to.include(`http.${TEST_STANDARD_REPO_URL}.extraheader=Authorization: Basic c3RkdXNlcjpzdGR0b2tlbjEyMw==`); + expect(pushArgStr).to.include('http.https://git.cloudmanager.adobe.com/myorg/.extraheader=Authorization: Basic c3RkdXNlcjpzdGR0b2tlbjEyMw=='); expect(pushArgStr).to.include(TEST_STANDARD_REPO_URL); expect(pushArgStr).to.not.include('stduser:stdtoken123@'); expect(pushArgStr).to.not.include('Bearer'); @@ -846,7 +859,7 @@ describe('CloudManagerClient', () => { const pullArgStr = getGitArgsStr(execFileSyncStub.firstCall); expect(pullArgStr).to.include('pull'); - expect(pullArgStr).to.include(`http.${TEST_STANDARD_REPO_URL}.extraheader=Authorization: Basic c3RkdXNlcjpzdGR0b2tlbjEyMw==`); + expect(pullArgStr).to.include('http.https://git.cloudmanager.adobe.com/myorg/.extraheader=Authorization: Basic c3RkdXNlcjpzdGR0b2tlbjEyMw=='); expect(pullArgStr).to.include(TEST_STANDARD_REPO_URL); expect(pullArgStr).to.not.include('stduser:stdtoken123@'); expect(pullArgStr).to.not.include('Bearer');