Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC of Platform API Plugin and an IDP CLI #103

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2358bf2
Added platform plugin
taras Oct 4, 2022
8961d50
Added IDP plugins
taras Oct 4, 2022
33ab30f
compile executables if they don not exist and report their location
cowboyd Oct 5, 2022
7e55d28
Make sure that dist url is used for executables
cowboyd Oct 5, 2022
6badab2
Use windows .exe extension when checking existing executable
cowboyd Oct 5, 2022
8701ad9
Hookup frontend
cowboyd Oct 5, 2022
0ae763a
fixup compile errors
cowboyd Oct 5, 2022
24edc79
add Curl installer
cowboyd Oct 7, 2022
f2ae4af
remove silly tests
cowboyd Oct 7, 2022
5f4a125
Remove last reference to `ldp`
cowboyd Oct 7, 2022
238df67
Don't call them Example components
cowboyd Oct 10, 2022
3bfbb41
customize the executable name and URL
cowboyd Oct 10, 2022
979c362
Add basic help and info commands
cowboyd Oct 10, 2022
6d6afe2
Add info command
cowboyd Oct 12, 2022
da40044
update executable compile permissions
dagda1 Oct 12, 2022
24db202
refactor from switch to cliffy
dagda1 Oct 12, 2022
9662dbc
remove `flags`
cowboyd Oct 12, 2022
d866f71
Make version more legible
cowboyd Oct 12, 2022
5af97a3
Add environments call
cowboyd Oct 13, 2022
51a4d2d
add idp create action (#116)
dagda1 Oct 14, 2022
edb07e0
Disable github entity provider
taras Oct 16, 2022
c0be5d8
Outputting repositories
taras Oct 16, 2022
14af7bc
Added clone command
taras Oct 16, 2022
fce26c7
Refactor to use PlatformAPI
taras Oct 17, 2022
33a7d7d
Upgrade node-deno
taras Oct 17, 2022
d98c43e
Fixed some of the types
taras Oct 18, 2022
7c081b6
Use humanitec keys as variables
taras Oct 18, 2022
6aaff1e
Fix types
taras Oct 18, 2022
8a45191
Add `location` parameter
cowboyd Oct 18, 2022
0f3187d
Add help text to the main plugin install page (#127)
cowboyd Oct 19, 2022
5ead130
Add streaming logs command and endpoint.
cowboyd Oct 24, 2022
c3e7721
🚨Fix TS Errors
cowboyd Oct 24, 2022
fc2a90c
Return more info from platform api in humanitec
minkimcello Oct 24, 2022
12fd7ae
Print out table for idp env
minkimcello Oct 24, 2022
6ed3233
Fixed merged conflict and updated dependencies
taras Nov 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@backstage/theme": "^0.2.16",
"@frontside/backstage-plugin-effection-inspector": "^0.1.2",
"@frontside/backstage-plugin-humanitec": "^0.3.1",
"@frontside/backstage-plugin-platform": "^0.1.0",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"history": "^5.0.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { FlatRoutes } from '@backstage/core-app-api';
import { orgPlugin } from '@backstage/plugin-org';
import { InspectorPage } from '@frontside/backstage-plugin-effection-inspector';
import { GraphiQLPage } from '@backstage/plugin-graphiql';
import { PlatformPage } from '@frontside/backstage-plugin-platform';

const app = createApp({
apis,
Expand Down Expand Up @@ -90,6 +91,7 @@ const routes = (
<Route path="/settings" element={<UserSettingsPage />} />
<Route path="/effection-inspector" element={<InspectorPage />} />
<Route path="/graphiql" element={<GraphiQLPage />} />
<Route path="/platform" element={<PlatformPage />}/>
</FlatRoutes>
);

Expand Down
6 changes: 5 additions & 1 deletion packages/backend/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'prefer-const': 'off'
}
});
1 change: 1 addition & 0 deletions packages/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/dist-bin/
1 change: 1 addition & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@frontside/backstage-plugin-graphql": "^0.4.1",
"@frontside/backstage-plugin-incremental-ingestion-backend": "*",
"@frontside/backstage-plugin-incremental-ingestion-github": "*",
"@frontside/backstage-plugin-platform-backend": "*",
"graphql-modules": "^2.1.0",
"@gitbeaker/node": "^34.6.0",
"@internal/plugin-healthcheck": "0.1.1",
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import healthcheck from './plugins/healthcheck';
import effectionInspector from './plugins/effection-inspector';
import humanitec from './plugins/humanitec';
import graphql from './plugins/graphql';
import idp from './plugins/idp';
import { PluginEnvironment } from './types';
import { CatalogClient } from '@backstage/catalog-client';

Expand Down Expand Up @@ -99,6 +100,7 @@ async function main() {
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
const humanitecEnv = useHotMemoize(module, () => createEnv('humanitec'));
const idpEnv = useHotMemoize(module, () => createEnv('idp'));

const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
Expand All @@ -111,6 +113,7 @@ async function main() {
apiRouter.use('/effection-inspector', await effectionInspector(effectionInspectorEnv));
apiRouter.use('/humanitec', await humanitec(humanitecEnv));
apiRouter.use('/graphql', await graphql(graphqlEnv));
apiRouter.use('/idp', await idp(idpEnv));
apiRouter.use(notFoundHandler());

const service = createServiceBuilder(module)
Expand Down
17 changes: 0 additions & 17 deletions packages/backend/src/plugins/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import {
} from '@backstage/plugin-catalog-backend';
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
import { IncrementalCatalogBuilder } from '@frontside/backstage-plugin-incremental-ingestion-backend';
import { GithubRepositoryEntityProvider } from '@frontside/backstage-plugin-incremental-ingestion-github';
import { Router } from 'express';
import { Duration } from 'luxon';
import { PluginEnvironment } from '../types';

export default async function createPlugin(
Expand All @@ -16,21 +14,6 @@ export default async function createPlugin(
// incremental builder receives builder because it'll register
// incremental entity providers with the builder
const incrementalBuilder = IncrementalCatalogBuilder.create(env, builder);

const githubRepositoryProvider = GithubRepositoryEntityProvider.create({
host: 'github.com',
searchQuery: "created:>1970-01-01 user:thefrontside",
config: env.config
})

incrementalBuilder.addIncrementalEntityProvider(
githubRepositoryProvider,
{
burstInterval: Duration.fromObject({ seconds: 3 }),
burstLength: Duration.fromObject({ seconds: 3 }),
restLength: Duration.fromObject({ day: 1 })
}
)

builder.addProcessor(new ScaffolderEntitiesProcessor());

Expand Down
78 changes: 78 additions & 0 deletions packages/backend/src/plugins/idp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { Router } from 'express';
import { createRouter } from '@frontside/backstage-plugin-platform-backend';
import { createHumanitecPlatformApi } from '@frontside/backstage-plugin-humanitec-common';
import { PluginEnvironment } from '../types';
import { Entity } from '@backstage/catalog-model';

export default async function createPlugin({
config,
logger,
discovery,
catalog,
}: PluginEnvironment): Promise<Router> {
return await createRouter({
executableName: 'idp',
logger,
discovery,
appURL: `${config.getString('app.baseUrl')}/platform`,
catalog,
platform: {
async getRepositoryUrls(ref) {
const entity = await ref.load();

if (entity) {
const slug = getGithubProjectSlug(entity);
return {
ssh: `git@github.com:${slug}.git`,
https: `https://github/${slug}.git`
}
}

return null;
},
async getRepositories() {
const { items: entities } = await catalog.getEntities();

const repositories = entities.flatMap(entity => {
const slug = getGithubProjectSlug(entity);
if (slug) {
return [{
componentRef: getComponentRef(entity),
slug,
description: entity.metadata.description,
url: `https://github/${slug}`,
}]
}
return [];
});
return {
hasNextPage: false,
hasPreviousPage: false,
beginCursor: '',
endCursor: '',
items: repositories.map(r => ({
cursor: '',
value: r
}))
}
},
...createHumanitecPlatformApi({
token: config.getString('humanitec.token'),
}),
},
});
}

function getGithubProjectSlug(entity: Entity) {
return entity.metadata
&& entity.metadata.annotations
&& entity.metadata.annotations["github.com/project-slug"];
}

function getComponentRef(entity: Entity) {
return [
entity.kind !== 'Component' ? entity.kind.toLowerCase() : '',
entity.metadata.namespace && entity.metadata.namespace !== 'default' ? entity.metadata.namespace : '',
entity.metadata.name
].join('')
}
16 changes: 15 additions & 1 deletion plugins/humanitec-common/src/clients/humanitec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ export function createHumanitecClient({ orgId, token }: { token: string; orgId:
const result = await _fetch('GET', `apps/${appId}/envs/${envId}/resources`);
return ResourcesResponsePayload.parse(result);
},

async getEnvironmentLogsSnapshot(appId: string, envId: string) {
type Message = {
workload_id: string;
container_id: string;
payload: string;
level: string
};

return await _fetch<Message[]>('GET', `apps/${appId}/envs/${envId.toLowerCase()}/logs?limit=100&invert=true`);

},
buildUrl(params: URLs) {
const baseUrl = `https://app.humanitec.io/orgs/${orgId}`;
switch (params.resource) {
Expand Down Expand Up @@ -89,6 +101,8 @@ export function createHumanitecClient({ orgId, token }: { token: string; orgId:

if (r.ok) {
return await r.json() as R;
} else {
console.dir({ error: r.statusText });
}

throw new FetchError(`Fetch ${method} to ${url} failed.`, r);
Expand All @@ -98,4 +112,4 @@ export function createHumanitecClient({ orgId, token }: { token: string; orgId:
retry: async (e: FetchError) => e.status === 403
});
}
}
}
2 changes: 2 additions & 0 deletions plugins/humanitec-common/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const HUMANITEC_ORG_ID_ANNOTATION = "humanitec.com/orgId";
export const HUMANITEC_APP_ID_ANNOTATION = "humanitec.com/appId";
4 changes: 3 additions & 1 deletion plugins/humanitec-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ export * from './types/environment';
export * from './types/resources';
export * from './types/runtime';
export * from './clients/fetch-app-info';
export * from './clients/humanitec';
export * from './clients/humanitec';
export * from './platform-api';
export * from './constants';
68 changes: 68 additions & 0 deletions plugins/humanitec-common/src/platform-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { PlatformApi } from '@frontside/backstage-plugin-platform-backend';
import type { Entity } from '@backstage/catalog-model';

import { HUMANITEC_APP_ID_ANNOTATION, HUMANITEC_ORG_ID_ANNOTATION } from './constants';
import { createHumanitecClient } from './clients/humanitec';


export type HumanitecPlatformAPI = Pick<PlatformApi, 'getLogs' | 'getEnvironments'>
export function createHumanitecPlatformApi({ token }: { token: string }): HumanitecPlatformAPI {

return {
async *getLogs(ref, envId) {
const entity = await ref.load();
const { appId, orgId } = getHumanitecMetadata(entity);
const client = createHumanitecClient({ token, orgId });
const logs = await client.getEnvironmentLogsSnapshot(appId, envId);
const bound = Math.floor(logs.length * .5);
const send = logs.slice(0, bound);
const stream = logs.slice(bound, logs.length);
for (const message of send) {
yield message.payload;
}
for (const message of stream) {
await new Promise((resolve) => setTimeout(resolve, Math.random() * 1500));
yield message.payload;
}
},

async getEnvironments(ref) {
const entity = await ref.load();
const { appId, orgId } = getHumanitecMetadata(entity);
const client = createHumanitecClient({ token, orgId });
const environments = await client.getEnvironments(appId);
return {
hasNextPage: false,
hasPreviousPage: false,
beginCursor: '',
endCursor: '',
items: environments.map(env => ({
cursor: '',
value: {
id: env.id,
name: env.name,
type: env.type,
url: `https://app.humanitec.io/orgs/${orgId}/apps/${appId}/envs/${env.id}`
}
})),
};
}
}
}

function getHumanitecMetadata(entity: Entity) {
const {
[HUMANITEC_ORG_ID_ANNOTATION]: orgId,
[HUMANITEC_APP_ID_ANNOTATION]: appId,
} = entity.metadata.annotations ?? {};

if (!orgId) {
throw new Error(`${entity.kind}:${entity.metadata.name} is not a humanitec entity`);

}
if (!appId) {
throw new Error(`${entity.kind}:${entity.metadata.name} is not a humanitec entity`);

}
return { orgId, appId };
}
5 changes: 5 additions & 0 deletions plugins/platform-backend/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'prefer-const': 'off'
}
});
14 changes: 14 additions & 0 deletions plugins/platform-backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# platform-backend

Welcome to the platform-backend backend plugin!

_This plugin was created through the Backstage CLI_

## Getting started

Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn
start` in the root directory, and then navigating to [/platform-backend](http://localhost:3000/platform-backend).

You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory.
5 changes: 5 additions & 0 deletions plugins/platform-backend/cli/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"deno.enable": true,
"deno.lint": false,
"deno.unstable": true
}
Loading