Setup basic GitHub & GHE integration using CLI

Signed-off-by: Philipp Hugenroth <philipph@spotify.com>
This commit is contained in:
Philipp Hugenroth
2023-03-01 10:10:12 +01:00
parent 55f802caa6
commit 87dc2beb21
7 changed files with 126 additions and 69 deletions
@@ -20,10 +20,30 @@ import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import fetch from 'node-fetch';
import { Task } from '../../../../lib/tasks';
import { addUserEntity, updateConfigFile } from '../config';
import { APP_CONFIG_FILE, PATCH_FOLDER, USER_ENTITY_FILE } from '../files';
import { addUserEntity, updateConfigFile } from '../../config';
import { APP_CONFIG_FILE, PATCH_FOLDER, USER_ENTITY_FILE } from '../../files';
import { patch } from '../patch';
type Answers = {
username: string;
clientSecret: string;
clientId: string;
hasEnterprise: boolean;
enterpriseInstanceUrl?: string;
};
const catalogUserLocation = {
catalog: {
locations: [
{
type: 'file',
target: '../../user-info.yaml',
rules: [{ allow: ['User'] }],
},
],
},
};
const validateCredentials = async (clientId: string, clientSecret: string) => {
try {
const app = new OAuthApp({
@@ -70,14 +90,6 @@ const getConfig = (answers: Answers) => {
};
};
type Answers = {
username: string;
clientSecret: string;
clientId: string;
hasEnterprise: boolean;
enterpriseInstanceUrl?: string;
};
export const github = async () => {
Task.log(`
To add GitHub authentication, you must create an OAuth App from the GitHub developer settings: ${chalk.blue(
@@ -147,7 +159,11 @@ export const github = async () => {
await Task.forItem(
'Updating',
APP_CONFIG_FILE,
async () => await updateConfigFile(APP_CONFIG_FILE, config),
async () =>
await updateConfigFile(APP_CONFIG_FILE, {
...config,
...catalogUserLocation,
}),
);
await Task.forItem(
'Creating',
@@ -18,8 +18,8 @@ import chalk from 'chalk';
import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import { Task } from '../../../../lib/tasks';
import { addUserEntity, updateConfigFile } from '../config';
import { APP_CONFIG_FILE, PATCH_FOLDER, USER_ENTITY_FILE } from '../files';
import { addUserEntity, updateConfigFile } from '../../config';
import { APP_CONFIG_FILE, PATCH_FOLDER, USER_ENTITY_FILE } from '../../files';
import { patch } from '../patch';
const getConfig = (answers: Answers) => {
@@ -17,7 +17,7 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as differ from 'diff';
import { PATCH_FOLDER } from './files';
import { PATCH_FOLDER } from '../files';
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
@@ -18,44 +18,14 @@ import { UserEntity } from '@backstage/catalog-model';
import * as fs from 'fs-extra';
import yaml from 'yaml';
type AuthConfig = {
auth: {
providers: {
[key: string]: {
development: {
clientId: string;
clientSecret: string;
enterpriseInstanceUrl?: string;
audience?: string;
};
};
};
};
};
const catalogUserLocation = {
catalog: {
locations: [
{
type: 'file',
target: '../../user-info.yaml',
rules: [{ allow: ['User'] }],
},
],
},
};
const readYaml = async (file: string) => {
return yaml.parse(await fs.readFile(file, 'utf8'));
};
export const updateConfigFile = async (
file: string,
authConfig: AuthConfig,
) => {
export const updateConfigFile = async <T>(file: string, config: T) => {
const content = fs.existsSync(file)
? { ...(await readYaml(file)), ...authConfig, ...catalogUserLocation }
: { ...authConfig, ...catalogUserLocation };
? { ...(await readYaml(file)), ...config }
: { ...config };
return await fs.writeFile(
file,
@@ -17,37 +17,82 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { Task } from '../../../../lib/tasks';
import { updateConfigFile } from '../../config';
import { APP_CONFIG_FILE } from '../../files';
type Answers = {
isEnterprise: boolean;
host: string;
apiBaseUrl?: string;
token: string;
};
const getConfig = ({ isEnterprise, host, apiBaseUrl, token }: Answers) => ({
integrations: {
github: isEnterprise
? [
{
host,
apiBaseUrl,
token,
},
]
: [
{
host,
token,
},
],
},
});
export const github = async () => {
const host = 'https://github.com';
const answers = await inquirer.prompt<{ hasEnterprise: boolean }>([
let host = 'github.com';
let apiBaseUrl: string | undefined;
// TODO(tudi2d): Is GitHub Enterprise a valid setup if there is no Authentication?
const { isEnterprise } = await inquirer.prompt<{
isEnterprise: Answers['isEnterprise'];
}>([
{
type: 'confirm',
name: 'hasEnterprise',
name: 'isEnterprise',
message: 'Are you using GitHub Enterprise?',
},
]);
if (answers.hasEnterprise) {
await inquirer.prompt<{ hasEnterprise: boolean }>([
{
type: 'confirm',
name: 'hasEnterprise',
message: 'Are you using GitHub Enterprise?',
},
{
type: 'input',
name: 'enterpriseInstanceUrl',
message: 'What is your GitHub Enterprise URL (e.g. ghe.example.net)?',
when: ({ hasEnterprise }) => hasEnterprise,
validate: (input: string) => Boolean(new URL(input)),
},
]);
if (isEnterprise) {
try {
const answers = await inquirer.prompt<
Pick<Answers, 'host' | 'apiBaseUrl'>
>([
{
type: 'input',
name: 'apiBaseUrl',
message:
'What is your GitHub Enterprise REST API URL (e.g. https://ghe.example.net/api/v3)?',
// TODO(tudi2d): Fetch API using OAuth Token if Auth was set up
validate: (input: string) => Boolean(new URL(input)),
},
{
type: 'input',
name: 'host',
message:
'What is your GitHub Enterprise Host (e.g. ghe.example.net)?',
// TODO(tudi2d): validate: Must the host be part of the REST API URL?
},
]);
host = answers.host;
apiBaseUrl = answers.apiBaseUrl;
} catch (err) {
return;
}
}
Task.log(`
To create new repositories in GitHub using Software Templates you first need to create a personal access token: ${chalk.blue(
`${host}/settings/tokens/new',
`https://${host}/settings/tokens/new`,
)}
Select the following scopes:
@@ -68,6 +113,32 @@ export const github = async () => {
You can find the full documentation page here: ${chalk.blue(
'https://backstage.io/docs/integrations/github/locations',
)}
`,
)}`);
`);
const { token } = await inquirer.prompt<{
token: Answers['token'];
}>([
{
type: 'input',
name: 'token',
message:
'Please insert your personal access token to setup the GitHub Integration',
// TODO(tudi2d): validate: Must the host be part of the REST API URL?
},
]);
const config = getConfig({
token,
isEnterprise,
host,
apiBaseUrl,
});
Task.log('Setting up GitHub Integration for you...');
await Task.forItem(
'Updating',
APP_CONFIG_FILE,
async () => await updateConfigFile(APP_CONFIG_FILE, config),
);
};
@@ -29,7 +29,7 @@ export async function integrations(): Promise<void> {
integration?: Integration;
}>([
{
// TODO(tudi2d): Should be multiple choice
// TODO(tudi2d): Let's start with one, but it should be multiple choice in the future
type: 'list',
name: 'integration',
message: 'Please select an integration:',