feat(github-actions): add support for GHES hosted repos
Signed-off-by: Adam Letizia <LetiziaAdam@JohnDeere.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-github-actions': minor
|
||||
---
|
||||
|
||||
add support for GHES hosted repositories
|
||||
@@ -64,6 +64,22 @@ const serviceEntityPage = (
|
||||
3. Run the app with `yarn start` and the backend with `yarn start-backend`.
|
||||
Then navigate to `/github-actions/` under any entity.
|
||||
|
||||
### Self-hosted / Enterprise GitHub
|
||||
|
||||
The plugin will try to use `backstage.io/source-location` or `backstage.io/managed-by-location`
|
||||
annotations to figure out the location of the source code.
|
||||
|
||||
1. Add the `host` and `apiBaseUrl` to your `app-config.yaml`
|
||||
|
||||
```yaml
|
||||
# app-config.yaml
|
||||
|
||||
integrations:
|
||||
github:
|
||||
- host: 'your-github-host.com'
|
||||
apiBaseUrl: 'https://api.your-github-host.com'
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- List workflow runs for a project
|
||||
|
||||
@@ -10,9 +10,9 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core-components';
|
||||
import { OAuthApi } from '@backstage/core-plugin-api';
|
||||
import { RestEndpointMethodTypes } from '@octokit/rest';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { ScmAuthApi } from '@backstage/integration-react';
|
||||
|
||||
// @public (undocumented)
|
||||
export enum BuildStatus {
|
||||
@@ -111,7 +111,7 @@ export const githubActionsApiRef: ApiRef<GithubActionsApi>;
|
||||
|
||||
// @public
|
||||
export class GithubActionsClient implements GithubActionsApi {
|
||||
constructor(options: { configApi: ConfigApi; githubAuthApi: OAuthApi });
|
||||
constructor(options: { configApi: ConfigApi; scmAuthApi: ScmAuthApi });
|
||||
// (undocumented)
|
||||
downloadJobLogsForWorkflowRun(options: {
|
||||
hostname?: string;
|
||||
|
||||
@@ -38,12 +38,14 @@
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/integration": "workspace:^",
|
||||
"@backstage/integration-react": "workspace:^",
|
||||
"@backstage/plugin-catalog-react": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@octokit/rest": "^19.0.3",
|
||||
"git-url-parse": "^13.0.0",
|
||||
"luxon": "^3.0.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
|
||||
import { readGithubIntegrationConfigs } from '@backstage/integration';
|
||||
import { ScmAuthApi } from '@backstage/integration-react';
|
||||
import { GithubActionsApi } from './GithubActionsApi';
|
||||
import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
|
||||
import { ConfigApi, OAuthApi } from '@backstage/core-plugin-api';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* A client for fetching information about GitHub actions.
|
||||
@@ -26,16 +27,22 @@ import { ConfigApi, OAuthApi } from '@backstage/core-plugin-api';
|
||||
*/
|
||||
export class GithubActionsClient implements GithubActionsApi {
|
||||
private readonly configApi: ConfigApi;
|
||||
private readonly githubAuthApi: OAuthApi;
|
||||
private readonly scmAuthApi: ScmAuthApi;
|
||||
|
||||
constructor(options: { configApi: ConfigApi; githubAuthApi: OAuthApi }) {
|
||||
constructor(options: { configApi: ConfigApi; scmAuthApi: ScmAuthApi }) {
|
||||
this.configApi = options.configApi;
|
||||
this.githubAuthApi = options.githubAuthApi;
|
||||
this.scmAuthApi = options.scmAuthApi;
|
||||
}
|
||||
|
||||
private async getOctokit(hostname?: string): Promise<Octokit> {
|
||||
// TODO: Get access token for the specified hostname
|
||||
const token = await this.githubAuthApi.getAccessToken(['repo']);
|
||||
private async getOctokit(hostname: string = 'github.com'): Promise<Octokit> {
|
||||
const { token } = await this.scmAuthApi.getCredentials({
|
||||
url: `https://${hostname}/`,
|
||||
additionalScope: {
|
||||
customScopes: {
|
||||
github: ['repo'],
|
||||
},
|
||||
},
|
||||
});
|
||||
const configs = readGithubIntegrationConfigs(
|
||||
this.configApi.getOptionalConfigArray('integrations.github') ?? [],
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { readGithubIntegrationConfigs } from '@backstage/integration';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
LinearProgress,
|
||||
@@ -28,13 +27,14 @@ import { GITHUB_ACTIONS_ANNOTATION } from '../getProjectNameFromEntity';
|
||||
import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns';
|
||||
import { WorkflowRunsTable } from '../WorkflowRunsTable';
|
||||
import { WorkflowRunStatus } from '../WorkflowRunStatus';
|
||||
import { configApiRef, errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
InfoCard,
|
||||
InfoCardVariants,
|
||||
Link,
|
||||
StructuredMetadataTable,
|
||||
} from '@backstage/core-components';
|
||||
import { getHostnameFromEntity } from '../getHostnameFromEntity';
|
||||
|
||||
const useStyles = makeStyles<Theme>({
|
||||
externalLinkIcon: {
|
||||
@@ -85,12 +85,8 @@ export const LatestWorkflowRunCard = (props: {
|
||||
}) => {
|
||||
const { branch = 'master', variant } = props;
|
||||
const { entity } = useEntity();
|
||||
const config = useApi(configApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGithubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const hostname = getHostnameFromEntity(entity);
|
||||
const [owner, repo] = (
|
||||
entity?.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] ?? '/'
|
||||
).split('/');
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { readGithubIntegrationConfigs } from '@backstage/integration';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
@@ -22,12 +21,7 @@ import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns';
|
||||
import { WorkflowRunStatus } from '../WorkflowRunStatus';
|
||||
import { Typography } from '@material-ui/core';
|
||||
|
||||
import {
|
||||
configApiRef,
|
||||
errorApiRef,
|
||||
useApi,
|
||||
useRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { errorApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
ErrorPanel,
|
||||
InfoCard,
|
||||
@@ -36,6 +30,7 @@ import {
|
||||
Table,
|
||||
} from '@backstage/core-components';
|
||||
import { buildRouteRef } from '../../routes';
|
||||
import { getHostnameFromEntity } from '../getHostnameFromEntity';
|
||||
|
||||
const firstLine = (message: string): string => message.split('\n')[0];
|
||||
|
||||
@@ -49,13 +44,9 @@ export const RecentWorkflowRunsCard = (props: {
|
||||
const { branch, dense = false, limit = 5, variant } = props;
|
||||
|
||||
const { entity } = useEntity();
|
||||
const config = useApi(configApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGithubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const hostname = getHostnameFromEntity(entity);
|
||||
|
||||
const [owner, repo] = (
|
||||
entity?.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] ?? '/'
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { readGithubIntegrationConfigs } from '@backstage/integration';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
@@ -44,8 +43,8 @@ import { WorkflowRunStatus } from '../WorkflowRunStatus';
|
||||
import { useWorkflowRunJobs } from './useWorkflowRunJobs';
|
||||
import { useWorkflowRunsDetails } from './useWorkflowRunsDetails';
|
||||
import { WorkflowRunLogs } from '../WorkflowRunLogs';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { Breadcrumbs, Link } from '@backstage/core-components';
|
||||
import { getHostnameFromEntity } from '../getHostnameFromEntity';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
root: {
|
||||
@@ -163,13 +162,9 @@ const JobsList = ({ jobs, entity }: { jobs?: Jobs; entity: Entity }) => {
|
||||
};
|
||||
|
||||
export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
|
||||
const config = useApi(configApiRef);
|
||||
const projectName = getProjectNameFromEntity(entity);
|
||||
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGithubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const hostname = getHostnameFromEntity(entity);
|
||||
const [owner, repo] = (projectName && projectName.split('/')) || [];
|
||||
const details = useWorkflowRunsDetails({ hostname, owner, repo });
|
||||
const jobs = useWorkflowRunJobs({ hostname, owner, repo });
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { LogViewer } from '@backstage/core-components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { readGithubIntegrationConfigs } from '@backstage/integration';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
@@ -35,6 +33,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import React from 'react';
|
||||
import { getProjectNameFromEntity } from '../getProjectNameFromEntity';
|
||||
import { useDownloadWorkflowRunLogs } from './useDownloadWorkflowRunLogs';
|
||||
import { getHostnameFromEntity } from '../getHostnameFromEntity';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
button: {
|
||||
@@ -75,14 +74,10 @@ export const WorkflowRunLogs = ({
|
||||
runId: number;
|
||||
inProgress: boolean;
|
||||
}) => {
|
||||
const config = useApi(configApiRef);
|
||||
const classes = useStyles();
|
||||
const projectName = getProjectNameFromEntity(entity);
|
||||
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGithubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const hostname = getHostnameFromEntity(entity);
|
||||
const [owner, repo] = (projectName && projectName.split('/')) || [];
|
||||
const jobLogs = useDownloadWorkflowRunLogs({
|
||||
hostname,
|
||||
|
||||
@@ -30,7 +30,6 @@ import SyncIcon from '@material-ui/icons/Sync';
|
||||
import { buildRouteRef } from '../../routes';
|
||||
import { getProjectNameFromEntity } from '../getProjectNameFromEntity';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { readGithubIntegrationConfigs } from '@backstage/integration';
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
@@ -38,7 +37,8 @@ import {
|
||||
TableColumn,
|
||||
Link,
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { getHostnameFromEntity } from '../getHostnameFromEntity';
|
||||
|
||||
const generatedColumns: TableColumn[] = [
|
||||
{
|
||||
@@ -164,12 +164,8 @@ export const WorkflowRunsTable = ({
|
||||
entity: Entity;
|
||||
branch?: string;
|
||||
}) => {
|
||||
const config = useApi(configApiRef);
|
||||
const projectName = getProjectNameFromEntity(entity);
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGithubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const hostname = getHostnameFromEntity(entity);
|
||||
const [owner, repo] = (projectName ?? '/').split('/');
|
||||
const [{ runs, ...tableProps }, { retry, setPage, setPageSize }] =
|
||||
useWorkflowRuns({
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
ANNOTATION_LOCATION,
|
||||
ANNOTATION_SOURCE_LOCATION,
|
||||
Entity,
|
||||
} from '@backstage/catalog-model';
|
||||
import gitUrlParse from 'git-url-parse';
|
||||
|
||||
export const getHostnameFromEntity = (entity: Entity) => {
|
||||
const location =
|
||||
entity?.metadata.annotations?.[ANNOTATION_SOURCE_LOCATION] ??
|
||||
entity?.metadata.annotations?.[ANNOTATION_LOCATION];
|
||||
|
||||
return location && location.startsWith('url:')
|
||||
? gitUrlParse(location.slice(4)).resource
|
||||
: '';
|
||||
};
|
||||
@@ -20,10 +20,10 @@ import {
|
||||
configApiRef,
|
||||
createPlugin,
|
||||
createApiFactory,
|
||||
githubAuthApiRef,
|
||||
createRoutableExtension,
|
||||
createComponentExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { scmAuthApiRef } from '@backstage/integration-react';
|
||||
|
||||
/** @public */
|
||||
export const githubActionsPlugin = createPlugin({
|
||||
@@ -31,9 +31,9 @@ export const githubActionsPlugin = createPlugin({
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: githubActionsApiRef,
|
||||
deps: { configApi: configApiRef, githubAuthApi: githubAuthApiRef },
|
||||
factory: ({ configApi, githubAuthApi }) =>
|
||||
new GithubActionsClient({ configApi, githubAuthApi }),
|
||||
deps: { configApi: configApiRef, scmAuthApi: scmAuthApiRef },
|
||||
factory: ({ configApi, scmAuthApi }) =>
|
||||
new GithubActionsClient({ configApi, scmAuthApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
|
||||
@@ -6871,6 +6871,7 @@ __metadata:
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/integration": "workspace:^"
|
||||
"@backstage/integration-react": "workspace:^"
|
||||
"@backstage/plugin-catalog-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
@@ -6885,6 +6886,7 @@ __metadata:
|
||||
"@types/node": ^16.11.26
|
||||
"@types/react": ^16.13.1 || ^17.0.0
|
||||
cross-fetch: ^3.1.5
|
||||
git-url-parse: ^13.0.0
|
||||
luxon: ^3.0.0
|
||||
msw: ^1.0.0
|
||||
react-use: ^17.2.4
|
||||
|
||||
Reference in New Issue
Block a user