Merge pull request #3313 from mfrinnstrom/scaffolder-extract-pushToRemote
Extract pushToRemote in scaffolder-backend
This commit is contained in:
@@ -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),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user