Allow templates to be located on non-default branch (#2839)
* Checkout branch if not on default * Use checkout Ref * Get first directory instead of relying on component_id * Checkout branch when clonning * Update test * Default to empty string * Added changeset * Added test for Cookie Cutter installed & nothing generated * Added comment
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Allow templates to be located on non-default branch
|
||||
@@ -76,7 +76,9 @@ describe('GitHubPreparer', () => {
|
||||
1,
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{},
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
},
|
||||
);
|
||||
});
|
||||
it('calls the clone command with the correct arguments for a repository when no path is provided', async () => {
|
||||
@@ -87,7 +89,9 @@ describe('GitHubPreparer', () => {
|
||||
1,
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{},
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
},
|
||||
);
|
||||
});
|
||||
it('return the temp directory with the path to the folder if it is specified', async () => {
|
||||
@@ -107,6 +111,7 @@ describe('GitHubPreparer', () => {
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.any(Function),
|
||||
|
||||
@@ -21,7 +21,7 @@ import { parseLocationAnnotation } from '../helpers';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { PreparerBase } from './types';
|
||||
import GitUriParser from 'git-url-parse';
|
||||
import { Clone, Cred } from 'nodegit';
|
||||
import { Clone, CloneOptions, Cred } from 'nodegit';
|
||||
|
||||
export class GithubPreparer implements PreparerBase {
|
||||
token?: string;
|
||||
@@ -52,17 +52,22 @@ export class GithubPreparer implements PreparerBase {
|
||||
template.spec.path ?? '.',
|
||||
);
|
||||
|
||||
const cloneOptions = token
|
||||
? {
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials() {
|
||||
return Cred.userpassPlaintextNew(token, 'x-oauth-basic');
|
||||
},
|
||||
let cloneOptions: CloneOptions = {
|
||||
checkoutBranch: parsedGitLocation.ref,
|
||||
};
|
||||
|
||||
if (token) {
|
||||
cloneOptions = {
|
||||
...cloneOptions,
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials() {
|
||||
return Cred.userpassPlaintextNew(token, 'x-oauth-basic');
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
await Clone.clone(repositoryCheckoutUrl, tempDir, cloneOptions);
|
||||
|
||||
|
||||
@@ -13,16 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
jest.mock('./helpers', () => ({ runDockerContainer: jest.fn() }));
|
||||
jest.mock('./helpers', () => ({
|
||||
runDockerContainer: jest.fn(),
|
||||
runCommand: jest.fn(),
|
||||
}));
|
||||
jest.mock('command-exists-promise', () => jest.fn());
|
||||
|
||||
import { CookieCutter } from './cookiecutter';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { RunDockerContainerOptions } from './helpers';
|
||||
import { RunDockerContainerOptions, RunCommandOptions } from './helpers';
|
||||
import { PassThrough } from 'stream';
|
||||
import Docker from 'dockerode';
|
||||
|
||||
const commandExists = require('command-exists-promise');
|
||||
|
||||
describe('CookieCutter Templater', () => {
|
||||
const cookie = new CookieCutter();
|
||||
const mockDocker = {} as Docker;
|
||||
@@ -32,6 +38,10 @@ describe('CookieCutter Templater', () => {
|
||||
runDockerContainer: jest.Mock<RunDockerContainerOptions>;
|
||||
} = require('./helpers');
|
||||
|
||||
jest
|
||||
.spyOn(fs, 'readdir')
|
||||
.mockImplementation(() => Promise.resolve(['newthing']));
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -174,4 +184,71 @@ describe('CookieCutter Templater', () => {
|
||||
dockerClient: mockDocker,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when cookiecutter is available', () => {
|
||||
beforeAll(() => {
|
||||
commandExists.mockImplementation(() => () => true);
|
||||
});
|
||||
|
||||
it('use the binary', async () => {
|
||||
const {
|
||||
runCommand,
|
||||
}: {
|
||||
runCommand: jest.Mock<RunCommandOptions>;
|
||||
} = require('./helpers');
|
||||
|
||||
const stream = new PassThrough();
|
||||
|
||||
const tempdir = await mkTemp();
|
||||
|
||||
const values = {
|
||||
owner: 'blobby',
|
||||
storePath: 'spotify/end-repo',
|
||||
component_id: 'newthing',
|
||||
};
|
||||
|
||||
await cookie.run({
|
||||
directory: tempdir,
|
||||
values,
|
||||
logStream: stream,
|
||||
dockerClient: mockDocker,
|
||||
});
|
||||
|
||||
expect(runCommand).toHaveBeenCalledWith({
|
||||
command: 'cookiecutter',
|
||||
args: expect.arrayContaining([
|
||||
'--no-input',
|
||||
'-o',
|
||||
tempdir,
|
||||
expect.stringContaining(`${tempdir}-result`),
|
||||
'--verbose',
|
||||
]),
|
||||
logStream: stream,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when nothing was generated', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(fs, 'readdir').mockImplementation(() => Promise.resolve([]));
|
||||
});
|
||||
|
||||
it('throws an error', async () => {
|
||||
const stream = new PassThrough();
|
||||
|
||||
const tempdir = await mkTemp();
|
||||
|
||||
return expect(
|
||||
cookie.run({
|
||||
directory: tempdir,
|
||||
values: {
|
||||
owner: 'blobby',
|
||||
storePath: 'spotify/end-repo',
|
||||
},
|
||||
logStream: stream,
|
||||
dockerClient: mockDocker,
|
||||
}),
|
||||
).rejects.toThrow(/Cookie Cutter did not generate anything/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,8 +78,16 @@ export class CookieCutter implements TemplaterBase {
|
||||
});
|
||||
}
|
||||
|
||||
// if cookiecutter was successful, resultDir will contain
|
||||
// exactly one directory.
|
||||
const [generated] = await fs.readdir(resultDir);
|
||||
|
||||
if (generated === undefined) {
|
||||
throw new Error('Cookie Cutter did not generate anything');
|
||||
}
|
||||
|
||||
return {
|
||||
resultDir: path.resolve(resultDir, options.values.component_id as string),
|
||||
resultDir: path.resolve(resultDir, generated),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user