Merge pull request #3313 from mfrinnstrom/scaffolder-extract-pushToRemote

Extract pushToRemote in scaffolder-backend
This commit is contained in:
Patrik Oldsberg
2020-11-18 20:52:33 +01:00
committed by GitHub
9 changed files with 259 additions and 451 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Extracted pushToRemote function for reuse between publishers
@@ -17,10 +17,13 @@
jest.mock('nodegit');
jest.mock('azure-devops-node-api/GitApi');
jest.mock('azure-devops-node-api/interfaces/GitInterfaces');
jest.mock('./helpers', () => ({
pushToRemoteUserPass: jest.fn(),
}));
import { AzurePublisher } from './azure';
import { GitApi } from 'azure-devops-node-api/GitApi';
import * as NodeGit from 'nodegit';
import { pushToRemoteUserPass } from './helpers';
const { mockGitApi } = require('azure-devops-node-api/GitApi') as {
mockGitApi: {
@@ -28,25 +31,6 @@ const { mockGitApi } = require('azure-devops-node-api/GitApi') as {
};
};
const {
Repository,
mockRepo,
mockIndex,
Signature,
Remote,
mockRemote,
Cred,
} = require('nodegit') as {
Repository: jest.Mocked<{ init: any }>;
Signature: jest.Mocked<{ now: any }>;
Cred: jest.Mocked<{ userpassPlaintextNew: any }>;
Remote: jest.Mocked<{ create: any }>;
mockIndex: jest.Mocked<NodeGit.Index>;
mockRepo: jest.Mocked<NodeGit.Repository>;
mockRemote: jest.Mocked<NodeGit.Remote>;
};
describe('Azure Publisher', () => {
const publisher = new AzurePublisher(new GitApi('', []), 'fake-token');
@@ -60,7 +44,7 @@ describe('Azure Publisher', () => {
remoteUrl: 'mockclone',
} as { remoteUrl: string });
await publisher.publish({
const result = await publisher.publish({
values: {
storePath: 'project/repo',
owner: 'bob',
@@ -68,109 +52,16 @@ describe('Azure Publisher', () => {
directory: '/tmp/test',
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
expect(mockGitApi.createRepository).toHaveBeenCalledWith(
{
name: 'repo',
},
'project',
);
});
});
describe('publish: createGitDirectory', () => {
const values = {
isOrg: true,
storePath: 'blam/test',
owner: 'lols',
};
const mockDir = '/tmp/test/dir';
mockGitApi.createRepository.mockResolvedValue({
remoteUrl: 'mockclone',
} as { remoteUrl: string });
it('should call init on the repo with the directory', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(Repository.init).toHaveBeenCalledWith(mockDir, 0);
});
it('should call refresh index on the index and write the new files', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(mockRepo.refreshIndex).toHaveBeenCalled();
});
it('should call add all files and write', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(mockIndex.addAll).toHaveBeenCalled();
expect(mockIndex.write).toHaveBeenCalled();
expect(mockIndex.writeTree).toHaveBeenCalled();
});
it('should create a commit with on head with the right name and commiter', async () => {
const mockSignature = { mockSignature: 'bloblly' };
Signature.now.mockReturnValue(mockSignature);
await publisher.publish({
values,
directory: mockDir,
});
expect(Signature.now).toHaveBeenCalledTimes(2);
expect(Signature.now).toHaveBeenCalledWith(
'Scaffolder',
'scaffolder@backstage.io',
);
expect(mockRepo.createCommit).toHaveBeenCalledWith(
'HEAD',
mockSignature,
mockSignature,
'initial commit',
'mockoid',
[],
);
});
it('creates a remote with the repo and remote', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(Remote.create).toHaveBeenCalledWith(
mockRepo,
'origin',
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
);
});
it('shoud push to the remote repo', async () => {
await publisher.publish({
values,
directory: mockDir,
});
const [remotes, { callbacks }] = mockRemote.push.mock
.calls[0] as NodeGit.PushOptions[];
expect(remotes).toEqual(['refs/heads/master:refs/heads/master']);
callbacks?.credentials?.();
expect(Cred.userpassPlaintextNew).toHaveBeenCalledWith(
'notempty',
'fake-token',
);
@@ -17,10 +17,9 @@
import { PublisherBase } from './types';
import { GitApi } from 'azure-devops-node-api/GitApi';
import { GitRepositoryCreateOptions } from 'azure-devops-node-api/interfaces/GitInterfaces';
import { pushToRemoteUserPass } from './helpers';
import { JsonValue } from '@backstage/config';
import { RequiredTemplateValues } from '../templater';
import { Repository, Remote, Signature, Cred } from 'nodegit';
export class AzurePublisher implements PublisherBase {
private readonly client: GitApi;
@@ -39,7 +38,7 @@ export class AzurePublisher implements PublisherBase {
directory: string;
}): Promise<{ remoteUrl: string }> {
const remoteUrl = await this.createRemote(values);
await this.pushToRemote(directory, remoteUrl);
await pushToRemoteUserPass(directory, remoteUrl, 'notempty', this.token);
return { remoteUrl };
}
@@ -54,29 +53,4 @@ export class AzurePublisher implements PublisherBase {
return repo.remoteUrl || '';
}
private async pushToRemote(directory: string, remote: string): Promise<void> {
const repo = await Repository.init(directory, 0);
const index = await repo.refreshIndex();
await index.addAll();
await index.write();
const oid = await index.writeTree();
await repo.createCommit(
'HEAD',
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
'initial commit',
oid,
[],
);
const remoteRepo = await Remote.create(repo, 'origin', remote);
await remoteRepo.push(['refs/heads/master:refs/heads/master'], {
callbacks: {
// Username can anything but the empty string according to: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page#use-a-pat
credentials: () => Cred.userpassPlaintextNew('notempty', this.token),
},
});
}
}
@@ -16,15 +16,18 @@
jest.mock('@octokit/rest');
jest.mock('nodegit');
jest.mock('./helpers', () => ({
pushToRemoteUserPass: jest.fn(),
}));
import { Octokit } from '@octokit/rest';
import * as NodeGit from 'nodegit';
import {
OctokitResponse,
ReposCreateInOrgResponseData,
UsersGetByUsernameResponseData,
} from '@octokit/types';
import { GithubPublisher } from './github';
import { pushToRemoteUserPass } from './helpers';
const { mockGithubClient } = require('@octokit/rest') as {
mockGithubClient: {
@@ -34,25 +37,6 @@ const { mockGithubClient } = require('@octokit/rest') as {
};
};
const {
Repository,
mockRepo,
mockIndex,
Signature,
Remote,
mockRemote,
Cred,
} = require('nodegit') as {
Repository: jest.Mocked<{ init: any }>;
Signature: jest.Mocked<{ now: any }>;
Cred: jest.Mocked<{ userpassPlaintextNew: any }>;
Remote: jest.Mocked<{ create: any }>;
mockIndex: jest.Mocked<NodeGit.Index>;
mockRepo: jest.Mocked<NodeGit.Repository>;
mockRemote: jest.Mocked<NodeGit.Remote>;
};
describe('GitHub Publisher', () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -72,8 +56,13 @@ describe('GitHub Publisher', () => {
clone_url: 'mockclone',
},
} as OctokitResponse<ReposCreateInOrgResponseData>);
mockGithubClient.users.getByUsername.mockResolvedValue({
data: {
type: 'Organization',
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
await publisher.publish({
const result = await publisher.publish({
values: {
storePath: 'blam/test',
owner: 'bob',
@@ -82,6 +71,7 @@ describe('GitHub Publisher', () => {
directory: '/tmp/test',
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({
org: 'blam',
name: 'test',
@@ -97,6 +87,12 @@ describe('GitHub Publisher', () => {
repo: 'test',
permission: 'admin',
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
'abc',
'x-oauth-basic',
);
});
it('should use octokit to create a repo in the authed user if the organisation property is not set', async () => {
@@ -111,7 +107,7 @@ describe('GitHub Publisher', () => {
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
await publisher.publish({
const result = await publisher.publish({
values: {
storePath: 'blam/test',
owner: 'bob',
@@ -120,6 +116,7 @@ describe('GitHub Publisher', () => {
directory: '/tmp/test',
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
expect(
mockGithubClient.repos.createForAuthenticatedUser,
).toHaveBeenCalledWith({
@@ -127,6 +124,12 @@ describe('GitHub Publisher', () => {
private: false,
});
expect(mockGithubClient.repos.addCollaborator).not.toHaveBeenCalled();
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
'abc',
'x-oauth-basic',
);
});
});
@@ -142,7 +145,7 @@ describe('GitHub Publisher', () => {
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
await publisher.publish({
const result = await publisher.publish({
values: {
storePath: 'blam/test',
owner: 'bob',
@@ -152,6 +155,7 @@ describe('GitHub Publisher', () => {
directory: '/tmp/test',
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
expect(
mockGithubClient.repos.createForAuthenticatedUser,
).toHaveBeenCalledWith({
@@ -165,113 +169,12 @@ describe('GitHub Publisher', () => {
username: 'bob',
permission: 'admin',
});
});
describe('publish: createGitDirectory', () => {
const values = {
storePath: 'blam/test',
owner: 'lols',
access: 'lols',
};
const mockDir = '/tmp/test/dir';
mockGithubClient.repos.createInOrg.mockResolvedValue({
data: {
clone_url: 'mockclone',
},
} as OctokitResponse<ReposCreateInOrgResponseData>);
mockGithubClient.users.getByUsername.mockResolvedValue({
data: {
type: 'Organization',
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
it('should call init on the repo with the directory', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(Repository.init).toHaveBeenCalledWith(mockDir, 0);
});
it('should call refresh index on the index and write the new files', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(mockRepo.refreshIndex).toHaveBeenCalled();
});
it('should call add all files and write', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(mockIndex.addAll).toHaveBeenCalled();
expect(mockIndex.write).toHaveBeenCalled();
expect(mockIndex.writeTree).toHaveBeenCalled();
});
it('should create a commit with on head with the right name and commiter', async () => {
const mockSignature = { mockSignature: 'bloblly' };
Signature.now.mockReturnValue(mockSignature);
await publisher.publish({
values,
directory: mockDir,
});
expect(Signature.now).toHaveBeenCalledTimes(2);
expect(Signature.now).toHaveBeenCalledWith(
'Scaffolder',
'scaffolder@backstage.io',
);
expect(mockRepo.createCommit).toHaveBeenCalledWith(
'HEAD',
mockSignature,
mockSignature,
'initial commit',
'mockoid',
[],
);
});
it('creates a remote with the repo and remote', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(Remote.create).toHaveBeenCalledWith(
mockRepo,
'origin',
'mockclone',
);
});
it('shoud push to the remote repo', async () => {
await publisher.publish({
values,
directory: mockDir,
});
const [remotes, { callbacks }] = mockRemote.push.mock
.calls[0] as NodeGit.PushOptions[];
expect(remotes).toEqual(['refs/heads/master:refs/heads/master']);
callbacks?.credentials?.();
expect(Cred.userpassPlaintextNew).toHaveBeenCalledWith(
'abc',
'x-oauth-basic',
);
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
'abc',
'x-oauth-basic',
);
});
});
@@ -294,7 +197,7 @@ describe('GitHub Publisher', () => {
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
await publisher.publish({
const result = await publisher.publish({
values: {
isOrg: true,
storePath: 'blam/test',
@@ -303,12 +206,19 @@ describe('GitHub Publisher', () => {
directory: '/tmp/test',
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({
org: 'blam',
name: 'test',
private: true,
visibility: 'internal',
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
'abc',
'x-oauth-basic',
);
});
});
@@ -331,7 +241,7 @@ describe('GitHub Publisher', () => {
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
await publisher.publish({
const result = await publisher.publish({
values: {
storePath: 'blam/test',
owner: 'bob',
@@ -339,12 +249,19 @@ describe('GitHub Publisher', () => {
directory: '/tmp/test',
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
expect(
mockGithubClient.repos.createForAuthenticatedUser,
).toHaveBeenCalledWith({
name: 'test',
private: true,
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
'abc',
'x-oauth-basic',
);
});
});
});
@@ -16,10 +16,9 @@
import { PublisherBase } from './types';
import { Octokit } from '@octokit/rest';
import { pushToRemoteUserPass } from './helpers';
import { JsonValue } from '@backstage/config';
import { RequiredTemplateValues } from '../templater';
import { Repository, Remote, Signature, Cred } from 'nodegit';
export type RepoVisibilityOptions = 'private' | 'internal' | 'public';
@@ -52,7 +51,12 @@ export class GithubPublisher implements PublisherBase {
directory: string;
}): Promise<{ remoteUrl: string }> {
const remoteUrl = await this.createRemote(values);
await this.pushToRemote(directory, remoteUrl);
await pushToRemoteUserPass(
directory,
remoteUrl,
this.token,
'x-oauth-basic',
);
return { remoteUrl };
}
@@ -104,29 +108,4 @@ export class GithubPublisher implements PublisherBase {
return data?.clone_url;
}
private async pushToRemote(directory: string, remote: string): Promise<void> {
const repo = await Repository.init(directory, 0);
const index = await repo.refreshIndex();
await index.addAll();
await index.write();
const oid = await index.writeTree();
await repo.createCommit(
'HEAD',
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
'initial commit',
oid,
[],
);
const remoteRepo = await Remote.create(repo, 'origin', remote);
await remoteRepo.push(['refs/heads/master:refs/heads/master'], {
callbacks: {
credentials: () => {
return Cred.userpassPlaintextNew(this.token, 'x-oauth-basic');
},
},
});
}
}
@@ -16,11 +16,14 @@
jest.mock('nodegit');
jest.mock('@gitbeaker/node');
jest.mock('./helpers', () => ({
pushToRemoteUserPass: jest.fn(),
}));
import { GitlabPublisher } from './gitlab';
import { Gitlab as GitlabAPI } from '@gitbeaker/core';
import { Gitlab } from '@gitbeaker/node';
import * as NodeGit from 'nodegit';
import { pushToRemoteUserPass } from './helpers';
const { mockGitlabClient } = require('@gitbeaker/node') as {
mockGitlabClient: {
@@ -30,25 +33,6 @@ const { mockGitlabClient } = require('@gitbeaker/node') as {
};
};
const {
Repository,
mockRepo,
mockIndex,
Signature,
Remote,
mockRemote,
Cred,
} = require('nodegit') as {
Repository: jest.Mocked<{ init: any }>;
Signature: jest.Mocked<{ now: any }>;
Cred: jest.Mocked<{ userpassPlaintextNew: any }>;
Remote: jest.Mocked<{ create: any }>;
mockIndex: jest.Mocked<NodeGit.Index>;
mockRepo: jest.Mocked<NodeGit.Repository>;
mockRemote: jest.Mocked<NodeGit.Remote>;
};
describe('GitLab Publisher', () => {
const publisher = new GitlabPublisher(new Gitlab({}), 'fake-token');
@@ -61,8 +45,11 @@ describe('GitLab Publisher', () => {
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 42,
} as { id: number });
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'mockclone',
} as { http_url_to_repo: string });
await publisher.publish({
const result = await publisher.publish({
values: {
isOrg: true,
storePath: 'blam/test',
@@ -71,10 +58,17 @@ describe('GitLab Publisher', () => {
directory: '/tmp/test',
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({
namespace_id: 42,
name: 'test',
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
'oauth2',
'fake-token',
);
});
it('should use gitbeaker to create a repo in the authed user if the namespace property is not set', async () => {
@@ -86,7 +80,7 @@ describe('GitLab Publisher', () => {
http_url_to_repo: 'mockclone',
} as { http_url_to_repo: string });
await publisher.publish({
const result = await publisher.publish({
values: {
storePath: 'blam/test',
owner: 'bob',
@@ -94,109 +88,15 @@ describe('GitLab Publisher', () => {
directory: '/tmp/test',
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
expect(mockGitlabClient.Users.current).toHaveBeenCalled();
expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({
namespace_id: 21,
name: 'test',
});
});
});
describe('publish: createGitDirectory', () => {
const values = {
isOrg: true,
storePath: 'blam/test',
owner: 'lols',
};
const mockDir = '/tmp/test/dir';
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'mockclone',
} as { http_url_to_repo: string });
it('should call init on the repo with the directory', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(Repository.init).toHaveBeenCalledWith(mockDir, 0);
});
it('should call refresh index on the index and write the new files', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(mockRepo.refreshIndex).toHaveBeenCalled();
});
it('should call add all files and write', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(mockIndex.addAll).toHaveBeenCalled();
expect(mockIndex.write).toHaveBeenCalled();
expect(mockIndex.writeTree).toHaveBeenCalled();
});
it('should create a commit with on head with the right name and commiter', async () => {
const mockSignature = { mockSignature: 'bloblly' };
Signature.now.mockReturnValue(mockSignature);
await publisher.publish({
values,
directory: mockDir,
});
expect(Signature.now).toHaveBeenCalledTimes(2);
expect(Signature.now).toHaveBeenCalledWith(
'Scaffolder',
'scaffolder@backstage.io',
);
expect(mockRepo.createCommit).toHaveBeenCalledWith(
'HEAD',
mockSignature,
mockSignature,
'initial commit',
'mockoid',
[],
);
});
it('creates a remote with the repo and remote', async () => {
await publisher.publish({
values,
directory: mockDir,
});
expect(Remote.create).toHaveBeenCalledWith(
mockRepo,
'origin',
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
);
});
it('shoud push to the remote repo', async () => {
await publisher.publish({
values,
directory: mockDir,
});
const [remotes, { callbacks }] = mockRemote.push.mock
.calls[0] as NodeGit.PushOptions[];
expect(remotes).toEqual(['refs/heads/master:refs/heads/master']);
callbacks?.credentials?.();
expect(Cred.userpassPlaintextNew).toHaveBeenCalledWith(
'oauth2',
'fake-token',
);
@@ -16,10 +16,9 @@
import { PublisherBase } from './types';
import { Gitlab } from '@gitbeaker/core';
import { pushToRemoteUserPass } from './helpers';
import { JsonValue } from '@backstage/config';
import { RequiredTemplateValues } from '../templater';
import { Repository, Remote, Signature, Cred } from 'nodegit';
export class GitlabPublisher implements PublisherBase {
private readonly client: Gitlab;
@@ -38,7 +37,7 @@ export class GitlabPublisher implements PublisherBase {
directory: string;
}): Promise<{ remoteUrl: string }> {
const remoteUrl = await this.createRemote(values);
await this.pushToRemote(directory, remoteUrl);
await pushToRemoteUserPass(directory, remoteUrl, 'oauth2', this.token);
return { remoteUrl };
}
@@ -63,28 +62,4 @@ export class GitlabPublisher implements PublisherBase {
return project?.http_url_to_repo;
}
private async pushToRemote(directory: string, remote: string): Promise<void> {
const repo = await Repository.init(directory, 0);
const index = await repo.refreshIndex();
await index.addAll();
await index.write();
const oid = await index.writeTree();
await repo.createCommit(
'HEAD',
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
'initial commit',
oid,
[],
);
const remoteRepo = await Remote.create(repo, 'origin', remote);
await remoteRepo.push(['refs/heads/master:refs/heads/master'], {
callbacks: {
credentials: () => Cred.userpassPlaintextNew('oauth2', this.token),
},
});
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
jest.mock('nodegit');
import * as NodeGit from 'nodegit';
import { pushToRemoteCred } from './helpers';
const {
Repository,
mockRepo,
mockIndex,
Signature,
Remote,
mockRemote,
Cred,
} = require('nodegit') as {
Repository: jest.Mocked<{ init: any }>;
Signature: jest.Mocked<{ now: any }>;
Cred: jest.Mocked<{ userpassPlaintextNew: any }>;
Remote: jest.Mocked<{ create: any }>;
mockIndex: jest.Mocked<NodeGit.Index>;
mockRepo: jest.Mocked<NodeGit.Repository>;
mockRemote: jest.Mocked<NodeGit.Remote>;
};
describe('pushToRemoteCred', () => {
beforeEach(() => {
jest.clearAllMocks();
});
const directory = '/tmp/test/dir';
const remote = 'mockclone';
const credentialsProvider = () =>
NodeGit.Cred.userpassPlaintextNew('username', 'password');
it('should call init on the repo with the directory', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(Repository.init).toHaveBeenCalledWith(directory, 0);
});
it('should call refresh index on the index and write the new files', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(mockRepo.refreshIndex).toHaveBeenCalled();
});
it('should call add all files and write', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(mockIndex.addAll).toHaveBeenCalled();
expect(mockIndex.write).toHaveBeenCalled();
expect(mockIndex.writeTree).toHaveBeenCalled();
});
it('should create a commit with on head with the right name and commiter', async () => {
const mockSignature = { mockSignature: 'bloblly' };
Signature.now.mockReturnValue(mockSignature);
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(Signature.now).toHaveBeenCalledTimes(2);
expect(Signature.now).toHaveBeenCalledWith(
'Scaffolder',
'scaffolder@backstage.io',
);
expect(mockRepo.createCommit).toHaveBeenCalledWith(
'HEAD',
mockSignature,
mockSignature,
'initial commit',
'mockoid',
[],
);
});
it('creates a remote with the repo and remote', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(Remote.create).toHaveBeenCalledWith(mockRepo, 'origin', 'mockclone');
});
it('shoud push to the remote repo', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
const [remotes, { callbacks }] = mockRemote.push.mock
.calls[0] as NodeGit.PushOptions[];
expect(remotes).toEqual(['refs/heads/master:refs/heads/master']);
callbacks?.credentials?.();
expect(Cred.userpassPlaintextNew).toHaveBeenCalledWith(
'username',
'password',
);
});
});
@@ -0,0 +1,55 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Repository, Remote, Signature, Cred } from 'nodegit';
export async function pushToRemoteCred(
directory: string,
remote: string,
credentialsProvider: () => Cred,
): Promise<void> {
const repo = await Repository.init(directory, 0);
const index = await repo.refreshIndex();
await index.addAll();
await index.write();
const oid = await index.writeTree();
await repo.createCommit(
'HEAD',
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
'initial commit',
oid,
[],
);
const remoteRepo = await Remote.create(repo, 'origin', remote);
await remoteRepo.push(['refs/heads/master:refs/heads/master'], {
callbacks: {
credentials: credentialsProvider,
},
});
}
export async function pushToRemoteUserPass(
directory: string,
remote: string,
username: string,
password: string,
): Promise<void> {
return pushToRemoteCred(directory, remote, () =>
Cred.userpassPlaintextNew(username, password),
);
}