feat(cli): create-app creates backend as well with PG support
This commit is contained in:
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
|
||||
import { promisify } from 'util';
|
||||
import chalk from 'chalk';
|
||||
import { Command } from 'commander';
|
||||
import inquirer, { Answers, Question } from 'inquirer';
|
||||
import inquirer, { Answers, Question, ChoiceBase, ui } from 'inquirer';
|
||||
import { exec as execCb } from 'child_process';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import os from 'os';
|
||||
@@ -104,8 +104,17 @@ export default async (cmd: Command): Promise<void> => {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'dbType',
|
||||
message: chalk.blue('Select database for the backend [required]'),
|
||||
// @ts-ignore
|
||||
choices: ['PostgreSQL', 'SQLite'],
|
||||
},
|
||||
];
|
||||
const answers: Answers = await inquirer.prompt(questions);
|
||||
answers['dbTypePG'] = answers.dbType === 'PostgreSQL';
|
||||
answers['dbTypeSqlite'] = answers.dbType === 'SQLite';
|
||||
|
||||
const templateDir = paths.resolveOwn('templates/default-app');
|
||||
const tempDir = resolvePath(os.tmpdir(), answers.name);
|
||||
|
||||
@@ -4,3 +4,19 @@ app:
|
||||
|
||||
organization:
|
||||
name: Acme Corporation
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7000
|
||||
listen: 0.0.0.0:7000
|
||||
cors:
|
||||
origin: http://localhost:3000
|
||||
methods: [GET, POST, PUT, DELETE]
|
||||
credentials: true
|
||||
|
||||
proxy:
|
||||
'/test':
|
||||
target: 'https://example.com'
|
||||
changeOrigin: true
|
||||
|
||||
techdocs:
|
||||
storageUrl: https://techdocs-mock-sites.storage.googleapis.com
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM node:12
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN yarn install --frozen-lockfile --production
|
||||
|
||||
CMD ["node", "packages/backend"]
|
||||
@@ -0,0 +1,70 @@
|
||||
# example-backend
|
||||
|
||||
This package is an EXAMPLE of a Backstage backend.
|
||||
|
||||
The main purpose of this package is to provide a test bed for Backstage plugins
|
||||
that have a backend part. Feel free to experiment locally or within your fork
|
||||
by adding dependencies and routes to this backend, to try things out.
|
||||
|
||||
Our goal is to eventually amend the create-app flow of the CLI, such that a
|
||||
production ready version of a backend skeleton is made alongside the frontend
|
||||
app. Until then, feel free to experiment here!
|
||||
|
||||
## Development
|
||||
|
||||
To run the example backend, first go to the project root and run
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn tsc
|
||||
yarn build
|
||||
```
|
||||
|
||||
You should only need to do this once.
|
||||
|
||||
After that, go to the `packages/backend` directory and run
|
||||
|
||||
```bash
|
||||
AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \
|
||||
AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \
|
||||
AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \
|
||||
AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \
|
||||
ROLLBAR_ACCOUNT_TOKEN=x \
|
||||
SENTRY_TOKEN=x \
|
||||
LOG_LEVEL=debug \
|
||||
yarn start
|
||||
```
|
||||
|
||||
Substitute `x` for actual values, or leave them as
|
||||
dummy values just to try out the backend without using the auth or sentry features.
|
||||
|
||||
The backend starts up on port 7000 per default.
|
||||
|
||||
## Populating The Catalog
|
||||
|
||||
If you want to use the catalog functionality, you need to add so called locations
|
||||
to the backend. These are places where the backend can find some entity descriptor
|
||||
data to consume and serve.
|
||||
|
||||
To get started, you can issue the following after starting the backend, from inside
|
||||
the `plugins/catalog-backend` directory:
|
||||
|
||||
```bash
|
||||
yarn mock-data
|
||||
```
|
||||
|
||||
You should then start seeing data on `localhost:7000/catalog/entities`.
|
||||
|
||||
The catalog currently runs in-memory only, so feel free to try it out, but it will
|
||||
need to be re-populated on next startup.
|
||||
|
||||
## Authentication
|
||||
|
||||
We chose [Passport](http://www.passportjs.org/) as authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/).
|
||||
|
||||
Read more about the [auth-backend](https://github.com/spotify/backstage/blob/master/plugins/auth-backend/README.md) and [how to add a new provider](https://github.com/spotify/backstage/blob/master/docs/auth/add-auth-provider.md)
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Backstage Readme](https://github.com/spotify/backstage/blob/master/README.md)
|
||||
- [Backstage Documentation](https://github.com/spotify/backstage/blob/master/docs/README.md)
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "12"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli backend:build",
|
||||
"build-image": "backstage-cli backend:build-image example-backend",
|
||||
"start": "backstage-cli backend:dev",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"clean": "backstage-cli clean",
|
||||
"migrate:create": "knex migrate:make -x ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^{{version}}",
|
||||
"@backstage/catalog-model": "^{{version}}",
|
||||
"@backstage/config": "^0.1.1-alpha.13",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-auth-backend": "^{{version}}",
|
||||
"@backstage/plugin-catalog-backend": "^{{version}}",
|
||||
"@backstage/plugin-identity-backend": "^{{version}}",
|
||||
"@backstage/plugin-proxy-backend": "^{{version}}",
|
||||
"@backstage/plugin-rollbar-backend": "^{{version}}",
|
||||
"@backstage/plugin-scaffolder-backend": "^{{version}}",
|
||||
"@backstage/plugin-sentry-backend": "^{{version}}",
|
||||
"@backstage/plugin-techdocs-backend": "^{{version}}",
|
||||
"@octokit/rest": "^18.0.0",
|
||||
"dockerode": "^3.2.0",
|
||||
"express": "^4.17.1",
|
||||
"knex": "^0.21.1",
|
||||
{{#if dbTypePG}}
|
||||
"pg": "^8.3.0",
|
||||
{{/if}}
|
||||
{{#if dbTypeSqlite}}
|
||||
"sqlite3": "^4.2.0",
|
||||
{{/if}}
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.15",
|
||||
"@types/dockerode": "^2.5.32",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express-serve-static-core": "^4.17.5",
|
||||
"@types/helmet": "^0.0.47"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 { PluginEnvironment } from './types';
|
||||
|
||||
describe('test', () => {
|
||||
it('unbreaks the test runner', () => {
|
||||
const unbreaker = {} as PluginEnvironment;
|
||||
expect(unbreaker).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Hi!
|
||||
*
|
||||
* Note that this is an EXAMPLE Backstage backend. Please check the README.
|
||||
*
|
||||
* Happy hacking!
|
||||
*/
|
||||
|
||||
import {
|
||||
createServiceBuilder,
|
||||
getRootLogger,
|
||||
useHotMemoize,
|
||||
} from '@backstage/backend-common';
|
||||
import { ConfigReader, AppConfig } from '@backstage/config';
|
||||
import { loadConfig } from '@backstage/config-loader';
|
||||
import knex from 'knex';
|
||||
import auth from './plugins/auth';
|
||||
import catalog from './plugins/catalog';
|
||||
import identity from './plugins/identity';
|
||||
import scaffolder from './plugins/scaffolder';
|
||||
import proxy from './plugins/proxy';
|
||||
import techdocs from './plugins/techdocs';
|
||||
import { PluginEnvironment } from './types';
|
||||
|
||||
function makeCreateEnv(loadedConfigs: AppConfig[]) {
|
||||
const config = ConfigReader.fromConfigs(loadedConfigs);
|
||||
|
||||
return (plugin: string): PluginEnvironment => {
|
||||
const logger = getRootLogger().child({ type: 'plugin', plugin });
|
||||
|
||||
{{#if dbTypePG}}
|
||||
const knexConfig = {
|
||||
client: 'pg',
|
||||
useNullAsDefault: true,
|
||||
connection: {
|
||||
host: process.env.PG_HOST,
|
||||
user: process.env.PG_USER,
|
||||
password: process.env.PG_PASSWORD,
|
||||
database: `backstage_plugin_${plugin}`,
|
||||
},
|
||||
};
|
||||
{{/if}}
|
||||
{{#if dbTypeSqlite}}
|
||||
const knexConfig = {
|
||||
client: 'sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
};
|
||||
{{/if}}
|
||||
const database = knex(knexConfig);
|
||||
database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
|
||||
resource.run('PRAGMA foreign_keys = ON', () => {});
|
||||
});
|
||||
return { logger, database, config };
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const configs = await loadConfig();
|
||||
const configReader = ConfigReader.fromConfigs(configs);
|
||||
const createEnv = makeCreateEnv(configs);
|
||||
|
||||
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
|
||||
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
|
||||
const authEnv = useHotMemoize(module, () => createEnv('auth'));
|
||||
const identityEnv = useHotMemoize(module, () => createEnv('identity'));
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(configReader)
|
||||
.addRouter('/catalog', await catalog(catalogEnv))
|
||||
.addRouter('/scaffolder', await scaffolder(scaffolderEnv))
|
||||
.addRouter('/auth', await auth(authEnv))
|
||||
.addRouter('/identity', await identity(identityEnv))
|
||||
.addRouter('/techdocs', await techdocs(techdocsEnv))
|
||||
.addRouter('/proxy', await proxy(proxyEnv));
|
||||
|
||||
await service.start().catch(err => {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.hot?.accept();
|
||||
main().catch(error => {
|
||||
console.error(`Backend failed to start up, ${error}`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 { createRouter } from '@backstage/plugin-auth-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
database,
|
||||
config,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config, database });
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 {
|
||||
createRouter,
|
||||
DatabaseEntitiesCatalog,
|
||||
DatabaseLocationsCatalog,
|
||||
DatabaseManager,
|
||||
HigherOrderOperations,
|
||||
LocationReaders,
|
||||
runPeriodically,
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { useHotCleanup } from '@backstage/backend-common';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
database,
|
||||
}: PluginEnvironment) {
|
||||
const locationReader = new LocationReaders(logger);
|
||||
|
||||
const db = await DatabaseManager.createDatabase(database, { logger });
|
||||
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
|
||||
const locationsCatalog = new DatabaseLocationsCatalog(db);
|
||||
const higherOrderOperation = new HigherOrderOperations(
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
locationReader,
|
||||
logger,
|
||||
);
|
||||
|
||||
useHotCleanup(
|
||||
module,
|
||||
runPeriodically(() => higherOrderOperation.refreshAllLocations(), 10000),
|
||||
);
|
||||
|
||||
return await createRouter({
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
higherOrderOperation,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { createRouter } from '@backstage/plugin-identity-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({ logger }: PluginEnvironment) {
|
||||
return await createRouter({ logger });
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
// @ts-ignore
|
||||
import { createRouter } from '@backstage/plugin-proxy-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config });
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 {
|
||||
CookieCutter,
|
||||
createRouter,
|
||||
FilePreparer,
|
||||
GithubPreparer,
|
||||
Preparers,
|
||||
GithubPublisher,
|
||||
CreateReactAppTemplater,
|
||||
Templaters,
|
||||
} from '@backstage/plugin-scaffolder-backend';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
import Docker from 'dockerode';
|
||||
|
||||
export default async function createPlugin({ logger }: PluginEnvironment) {
|
||||
const cookiecutterTemplater = new CookieCutter();
|
||||
const craTemplater = new CreateReactAppTemplater();
|
||||
const templaters = new Templaters();
|
||||
templaters.register('cookiecutter', cookiecutterTemplater);
|
||||
templaters.register('cra', craTemplater);
|
||||
|
||||
const filePreparer = new FilePreparer();
|
||||
const githubPreparer = new GithubPreparer();
|
||||
const preparers = new Preparers();
|
||||
|
||||
preparers.register('file', filePreparer);
|
||||
preparers.register('github', githubPreparer);
|
||||
|
||||
const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
|
||||
const publisher = new GithubPublisher({ client: githubClient });
|
||||
|
||||
const dockerClient = new Docker();
|
||||
return await createRouter({
|
||||
preparers,
|
||||
templaters,
|
||||
publisher,
|
||||
logger,
|
||||
dockerClient,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { createRouter } from '@backstage/plugin-techdocs-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({ logger }: PluginEnvironment) {
|
||||
return await createRouter({ logger });
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 Knex from 'knex';
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
export type PluginEnvironment = {
|
||||
logger: Logger;
|
||||
database: Knex;
|
||||
config: Config;
|
||||
};
|
||||
@@ -1,8 +1,14 @@
|
||||
{
|
||||
"extends": "@backstage/cli/config/tsconfig.json",
|
||||
"include": ["packages/*/src", "plugins/*/src", "plugins/*/dev"],
|
||||
"exclude": ["**/node_modules"],
|
||||
"include": [
|
||||
"packages/*/src",
|
||||
"plugins/*/src",
|
||||
"plugins/*/dev",
|
||||
"plugins/*/migrations"
|
||||
],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user