Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions packages/spacecat-shared-cloud-manager-client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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,
];
}
Expand Down Expand Up @@ -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');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,14 +278,27 @@ 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
expect(cloneArgsStr).to.not.include('stduser:stdtoken123@');
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 }),
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand Down
Loading