/ca/server.crt
35 |
36 | catalog:
37 | # Overrides the default list locations from app-config.yaml as these contain example data.
38 | # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details
39 | # on how to get entities into the catalog.
40 | locations: []
41 |
42 | tekton:
43 | baseUrl: https://example.domain.com
44 | authorizationBearerToken: 'xxx'
45 | dashboardBaseUrl: https://example.domain.com
46 |
47 | tekton:
48 | - baseUrl: https://example1.domain.com
49 | authorizationBearerToken: 'xxx'
50 | dashboardBaseUrl: https://example1.domain.com
51 | # multiple kubernetes clusters
52 | # - baseUrl: https://example2.domain.com
53 | # authorizationBearerToken: 'yyy'
54 | # dashboardBaseUrl: https://example2.domain.com
55 |
--------------------------------------------------------------------------------
/app-config.yaml:
--------------------------------------------------------------------------------
1 | app:
2 | title: Scaffolded Backstage App
3 | baseUrl: http://localhost:3000
4 |
5 | organization:
6 | name: My Company
7 |
8 | backend:
9 | # Used for enabling authentication, secret is shared by all backend plugins
10 | # See https://backstage.io/docs/tutorials/backend-to-backend-auth for
11 | # information on the format
12 | # auth:
13 | # keys:
14 | # - secret: ${BACKEND_SECRET}
15 | baseUrl: http://localhost:7007
16 | listen:
17 | port: 7007
18 | # Uncomment the following host directive to bind to all IPv4 interfaces and
19 | # not just the baseUrl hostname.
20 | # host: 0.0.0.0
21 | csp:
22 | connect-src: ["'self'", 'http:', 'https:']
23 | # Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
24 | # Default Helmet Content-Security-Policy values can be removed by setting the key to false
25 | cors:
26 | origin: http://localhost:3000
27 | methods: [GET, POST, PUT, DELETE]
28 | credentials: true
29 | # This is for local developement only, it is not recommended to use this in production
30 | # The production database configuration is stored in app-config.production.yaml
31 | database:
32 | client: better-sqlite3
33 | connection: ':memory:'
34 | cache:
35 | store: memory
36 | # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
37 |
38 | integrations:
39 | github:
40 | - host: github.com
41 | # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information
42 | # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration
43 | token: ${GITHUB_TOKEN}
44 | ### Example for how to add your GitHub Enterprise instance using the API:
45 | # - host: ghe.example.net
46 | # apiBaseUrl: https://ghe.example.net/api/v3
47 | # token: ${GHE_TOKEN}
48 |
49 | proxy:
50 | '/test':
51 | target: 'https://example.com'
52 | changeOrigin: true
53 |
54 | # Reference documentation http://backstage.io/docs/features/techdocs/configuration
55 | # Note: After experimenting with basic setup, use CI/CD to generate docs
56 | # and an external cloud storage when deploying TechDocs for production use-case.
57 | # https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
58 | techdocs:
59 | builder: 'local' # Alternatives - 'external'
60 | generator:
61 | runIn: 'docker' # Alternatives - 'local'
62 | publisher:
63 | type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
64 |
65 | auth:
66 | # see https://backstage.io/docs/auth/ to learn about auth providers
67 | providers: {}
68 |
69 | scaffolder:
70 | # see https://backstage.io/docs/features/software-templates/configuration for software template options
71 |
72 | catalog:
73 | import:
74 | entityFilename: catalog-info.yaml
75 | pullRequestBranchName: backstage-integration
76 | rules:
77 | - allow: [Component, System, API, Resource, Location]
78 | locations:
79 | # Local example data, file locations are relative to the backend process, typically `packages/backend`
80 | - type: file
81 | target: ../../examples/entities.yaml
82 |
83 | # Local example template
84 | - type: file
85 | target: ../../examples/template/template.yaml
86 | rules:
87 | - allow: [Template]
88 |
89 | # Local example organizational data
90 | - type: file
91 | target: ../../examples/org.yaml
92 | rules:
93 | - allow: [User, Group]
94 |
95 | ## Uncomment these lines to add more example data
96 | - type: url
97 | target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml
98 |
99 | ## Uncomment these lines to add an example org
100 | - type: url
101 | target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml
102 | rules:
103 | - allow: [User, Group]
104 |
105 | tekton:
106 | - name: cluster1
107 | baseUrl: https://example1.domain.com
108 | authorizationBearerToken: 'xxx'
109 | dashboardBaseUrl: https://example1.domain.com
110 | externalLogs:
111 | - enabled: true
112 | urlTemplate: https://externalBaseUrl/$namespace/$taskRunPodName/$stepContainer.txt
113 | headers: [
114 | "Content-Type",
115 | "plain/text",
116 | "Authorization",
117 | "AWS AKIAIOSFODNN7EXAMPLE:qgk2+6Sv9/oM7G3qLEjTH1a1l1g="
118 | ]
119 | # multiple kubernetes clusters
120 | # - name: cluster2
121 | # baseUrl: https://example2.domain.com
122 | # authorizationBearerToken: 'yyy'
123 | # dashboardBaseUrl: https://example2.domain.com
--------------------------------------------------------------------------------
/backstage.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.22.1"
3 | }
4 |
--------------------------------------------------------------------------------
/catalog-info.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: backstage.io/v1alpha1
2 | kind: Component
3 | metadata:
4 | name: jquad-backstage
5 | description: An example of a Backstage application.
6 | # Example for optional annotations
7 | # annotations:
8 | # github.com/project-slug: backstage/backstage
9 | # backstage.io/techdocs-ref: dir:.
10 | spec:
11 | type: website
12 | owner: john@example.com
13 | lifecycle: experimental
14 |
--------------------------------------------------------------------------------
/examples/entities.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system
3 | apiVersion: backstage.io/v1alpha1
4 | kind: System
5 | metadata:
6 | name: examples
7 | spec:
8 | owner: guests
9 | ---
10 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component
11 | apiVersion: backstage.io/v1alpha1
12 | kind: Component
13 | metadata:
14 | name: example-website
15 | spec:
16 | type: website
17 | lifecycle: experimental
18 | owner: guests
19 | system: examples
20 | providesApis: [example-grpc-api]
21 | ---
22 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api
23 | apiVersion: backstage.io/v1alpha1
24 | kind: API
25 | metadata:
26 | name: example-grpc-api
27 | spec:
28 | type: grpc
29 | lifecycle: experimental
30 | owner: guests
31 | system: examples
32 | definition: |
33 | syntax = "proto3";
34 |
35 | service Exampler {
36 | rpc Example (ExampleMessage) returns (ExampleMessage) {};
37 | }
38 |
39 | message ExampleMessage {
40 | string example = 1;
41 | };
42 |
--------------------------------------------------------------------------------
/examples/org.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
3 | apiVersion: backstage.io/v1alpha1
4 | kind: User
5 | metadata:
6 | name: guest
7 | spec:
8 | memberOf: [guests]
9 | ---
10 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group
11 | apiVersion: backstage.io/v1alpha1
12 | kind: Group
13 | metadata:
14 | name: guests
15 | spec:
16 | type: team
17 | children: []
18 |
--------------------------------------------------------------------------------
/examples/template/content/catalog-info.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: backstage.io/v1alpha1
2 | kind: Component
3 | metadata:
4 | name: ${{ values.name | dump }}
5 | spec:
6 | type: service
7 | owner: user:guest
8 | lifecycle: experimental
9 |
--------------------------------------------------------------------------------
/examples/template/content/index.js:
--------------------------------------------------------------------------------
1 | console.log('Hello from ${{ values.name }}!');
2 |
--------------------------------------------------------------------------------
/examples/template/content/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "${{ values.name }}",
3 | "private": true,
4 | "dependencies": {}
5 | }
6 |
--------------------------------------------------------------------------------
/examples/template/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: scaffolder.backstage.io/v1beta3
2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
3 | kind: Template
4 | metadata:
5 | name: example-nodejs-template
6 | title: Example Node.js Template
7 | description: An example template for the scaffolder that creates a simple Node.js service
8 | spec:
9 | owner: user:guest
10 | type: service
11 |
12 | # These parameters are used to generate the input form in the frontend, and are
13 | # used to gather input data for the execution of the template.
14 | parameters:
15 | - title: Fill in some steps
16 | required:
17 | - name
18 | properties:
19 | name:
20 | title: Name
21 | type: string
22 | description: Unique name of the component
23 | ui:autofocus: true
24 | ui:options:
25 | rows: 5
26 | - title: Choose a location
27 | required:
28 | - repoUrl
29 | properties:
30 | repoUrl:
31 | title: Repository Location
32 | type: string
33 | ui:field: RepoUrlPicker
34 | ui:options:
35 | allowedHosts:
36 | - github.com
37 |
38 | # These steps are executed in the scaffolder backend, using data that we gathered
39 | # via the parameters above.
40 | steps:
41 | # Each step executes an action, in this case one templates files into the working directory.
42 | - id: fetch-base
43 | name: Fetch Base
44 | action: fetch:template
45 | input:
46 | url: ./content
47 | values:
48 | name: ${{ parameters.name }}
49 |
50 | # This step publishes the contents of the working directory to GitHub.
51 | - id: publish
52 | name: Publish
53 | action: publish:github
54 | input:
55 | allowedHosts: ['github.com']
56 | description: This is ${{ parameters.name }}
57 | repoUrl: ${{ parameters.repoUrl }}
58 |
59 | # The final step is to register our new component in the catalog.
60 | - id: register
61 | name: Register
62 | action: catalog:register
63 | input:
64 | repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
65 | catalogInfoPath: '/catalog-info.yaml'
66 |
67 | # Outputs are displayed to the user after a successful execution of the template.
68 | output:
69 | links:
70 | - title: Repository
71 | url: ${{ steps['publish'].output.remoteUrl }}
72 | - title: Open in catalog
73 | icon: catalog
74 | entityRef: ${{ steps['register'].output.entityRef }}
75 |
--------------------------------------------------------------------------------
/img/tekton-horizontal-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jquad-group/backstage-jquad/8d6de94cdde46bc2fc85164d087b36b3be8d8521/img/tekton-horizontal-color.png
--------------------------------------------------------------------------------
/img/tekton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jquad-group/backstage-jquad/8d6de94cdde46bc2fc85164d087b36b3be8d8521/img/tekton.png
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["packages/*", "plugins/*"],
3 | "npmClient": "yarn",
4 | "version": "0.1.0",
5 | "$schema": "node_modules/lerna/schemas/lerna-schema.json"
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "version": "1.0.0",
4 | "private": true,
5 | "engines": {
6 | "node": "18 || 20"
7 | },
8 | "scripts": {
9 | "dev": "concurrently \"yarn start\" \"yarn start-backend\"",
10 | "start": "yarn workspace app start",
11 | "start-backend": "yarn workspace backend start",
12 | "build:backend": "yarn workspace backend build",
13 | "build:all": "backstage-cli repo build --all",
14 | "build-image": "yarn workspace backend build-image",
15 | "tsc": "tsc",
16 | "tsc:full": "tsc --skipLibCheck false --incremental false",
17 | "clean": "backstage-cli repo clean",
18 | "test": "backstage-cli repo test",
19 | "test:all": "backstage-cli repo test --coverage",
20 | "test:e2e": "playwright test",
21 | "fix": "backstage-cli repo fix",
22 | "lint": "backstage-cli repo lint --since origin/master",
23 | "lint:all": "backstage-cli repo lint",
24 | "prettier:check": "prettier --check .",
25 | "new": "backstage-cli new --scope internal"
26 | },
27 | "workspaces": {
28 | "packages": [
29 | "packages/*",
30 | "plugins/*"
31 | ]
32 | },
33 | "devDependencies": {
34 | "@backstage/cli": "^0.25.1",
35 | "@backstage/e2e-test-utils": "^0.1.0",
36 | "@playwright/test": "^1.32.3",
37 | "@spotify/prettier-config": "^12.0.0",
38 | "concurrently": "^8.2.2",
39 | "lerna": "^7.3.0",
40 | "node-gyp": "^9.0.0",
41 | "prettier": "^2.3.2",
42 | "typescript": "~5.2.0"
43 | },
44 | "resolutions": {
45 | "@types/react": "^18",
46 | "@types/react-dom": "^18"
47 | },
48 | "prettier": "@spotify/prettier-config",
49 | "lint-staged": {
50 | "*.{js,jsx,ts,tsx,mjs,cjs}": [
51 | "eslint --fix",
52 | "prettier --write"
53 | ],
54 | "*.{json,md}": [
55 | "prettier --write"
56 | ]
57 | },
58 | "dependencies": {
59 | "@material-ui/core": "4.11.2",
60 | "react": "^18.2.0",
61 | "react-dom": "^18.2.0"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/README.md:
--------------------------------------------------------------------------------
1 | # The Packages Folder
2 |
3 | This is where your own applications and centrally managed libraries live, each
4 | in a separate folder of its own.
5 |
6 | From the start there's an `app` folder (for the frontend) and a `backend` folder
7 | (for the Node backend), but you can also add more modules in here that house
8 | your core additions and adaptations, such as themes, common React component
9 | libraries, utilities, and similar.
10 |
--------------------------------------------------------------------------------
/packages/app/.eslintignore:
--------------------------------------------------------------------------------
1 | public
2 |
--------------------------------------------------------------------------------
/packages/app/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
2 |
--------------------------------------------------------------------------------
/packages/app/e2e-tests/app.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 The Backstage Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { test, expect } from '@playwright/test';
18 |
19 | test('App should render the welcome page', async ({ page }) => {
20 | await page.goto('/');
21 |
22 | await expect(page.getByText('My Company Catalog')).toBeVisible();
23 | });
24 |
--------------------------------------------------------------------------------
/packages/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.0.0",
4 | "private": true,
5 | "bundled": true,
6 | "backstage": {
7 | "role": "frontend"
8 | },
9 | "scripts": {
10 | "start": "backstage-cli package start",
11 | "build": "backstage-cli package build",
12 | "clean": "backstage-cli package clean",
13 | "test": "backstage-cli package test",
14 | "lint": "backstage-cli package lint"
15 | },
16 | "dependencies": {
17 | "@backstage/app-defaults": "^1.4.7",
18 | "@backstage/catalog-model": "^1.4.3",
19 | "@backstage/cli": "^0.25.1",
20 | "@backstage/core-app-api": "^1.11.3",
21 | "@backstage/core-components": "^0.13.10",
22 | "@backstage/core-plugin-api": "^1.8.2",
23 | "@backstage/integration-react": "^1.1.23",
24 | "@backstage/plugin-api-docs": "^0.10.3",
25 | "@backstage/plugin-catalog": "^1.16.1",
26 | "@backstage/plugin-catalog-common": "^1.0.20",
27 | "@backstage/plugin-catalog-graph": "^0.3.3",
28 | "@backstage/plugin-catalog-import": "^0.10.5",
29 | "@backstage/plugin-catalog-react": "^1.9.3",
30 | "@backstage/plugin-github-actions": "^0.6.10",
31 | "@backstage/plugin-kubernetes": "^0.11.4",
32 | "@backstage/plugin-org": "^0.6.19",
33 | "@backstage/plugin-permission-react": "^0.4.19",
34 | "@backstage/plugin-scaffolder": "^1.17.1",
35 | "@backstage/plugin-search": "^1.4.5",
36 | "@backstage/plugin-search-react": "^1.7.5",
37 | "@backstage/plugin-tech-radar": "^0.6.12",
38 | "@backstage/plugin-techdocs": "^1.9.3",
39 | "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.4",
40 | "@backstage/plugin-techdocs-react": "^1.1.15",
41 | "@backstage/plugin-user-settings": "^0.8.0",
42 | "@backstage/theme": "^0.5.0",
43 | "@material-ui/core": "^4.12.2",
44 | "@material-ui/icons": "^4.9.1",
45 | "history": "^5.0.0",
46 | "react": "^18.0.2",
47 | "react-dom": "^18.0.2",
48 | "react-router": "^6.3.0",
49 | "react-router-dom": "^6.3.0",
50 | "react-use": "^17.2.4"
51 | },
52 | "devDependencies": {
53 | "@backstage/test-utils": "^1.4.7",
54 | "@playwright/test": "^1.32.3",
55 | "@testing-library/dom": "^9.0.0",
56 | "@testing-library/jest-dom": "^6.0.0",
57 | "@testing-library/react": "^14.0.0",
58 | "@testing-library/user-event": "^14.0.0",
59 | "@types/react-dom": "*",
60 | "cross-env": "^7.0.0"
61 | },
62 | "browserslist": {
63 | "production": [
64 | ">0.2%",
65 | "not dead",
66 | "not op_mini all"
67 | ],
68 | "development": [
69 | "last 1 chrome version",
70 | "last 1 firefox version",
71 | "last 1 safari version"
72 | ]
73 | },
74 | "files": [
75 | "dist"
76 | ]
77 | }
78 |
--------------------------------------------------------------------------------
/packages/app/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jquad-group/backstage-jquad/8d6de94cdde46bc2fc85164d087b36b3be8d8521/packages/app/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/packages/app/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jquad-group/backstage-jquad/8d6de94cdde46bc2fc85164d087b36b3be8d8521/packages/app/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/packages/app/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jquad-group/backstage-jquad/8d6de94cdde46bc2fc85164d087b36b3be8d8521/packages/app/public/favicon-16x16.png
--------------------------------------------------------------------------------
/packages/app/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jquad-group/backstage-jquad/8d6de94cdde46bc2fc85164d087b36b3be8d8521/packages/app/public/favicon-32x32.png
--------------------------------------------------------------------------------
/packages/app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jquad-group/backstage-jquad/8d6de94cdde46bc2fc85164d087b36b3be8d8521/packages/app/public/favicon.ico
--------------------------------------------------------------------------------
/packages/app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
15 |
20 |
21 |
22 |
27 |
33 |
39 |
44 | <%= config.getString('app.title') %>
45 |
46 |
47 |
48 |
49 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/packages/app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Backstage",
3 | "name": "Backstage",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "48x48",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/packages/app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/packages/app/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/app/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, waitFor } from '@testing-library/react';
3 | import App from './App';
4 |
5 | describe('App', () => {
6 | it('should render', async () => {
7 | process.env = {
8 | NODE_ENV: 'test',
9 | APP_CONFIG: [
10 | {
11 | data: {
12 | app: { title: 'Test' },
13 | backend: { baseUrl: 'http://localhost:7007' },
14 | techdocs: {
15 | storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
16 | },
17 | },
18 | context: 'test',
19 | },
20 | ] as any,
21 | };
22 |
23 | const rendered = render();
24 |
25 | await waitFor(() => {
26 | expect(rendered.baseElement).toBeInTheDocument();
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/packages/app/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Navigate, Route } from 'react-router-dom';
3 | import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs';
4 | import {
5 | CatalogEntityPage,
6 | CatalogIndexPage,
7 | catalogPlugin,
8 | } from '@backstage/plugin-catalog';
9 | import {
10 | CatalogImportPage,
11 | catalogImportPlugin,
12 | } from '@backstage/plugin-catalog-import';
13 | import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
14 | import { orgPlugin } from '@backstage/plugin-org';
15 | import { SearchPage } from '@backstage/plugin-search';
16 | import { TechRadarPage } from '@backstage/plugin-tech-radar';
17 | import {
18 | TechDocsIndexPage,
19 | techdocsPlugin,
20 | TechDocsReaderPage,
21 | } from '@backstage/plugin-techdocs';
22 | import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
23 | import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
24 | import { UserSettingsPage } from '@backstage/plugin-user-settings';
25 | import { apis } from './apis';
26 | import { entityPage } from './components/catalog/EntityPage';
27 | import { searchPage } from './components/search/SearchPage';
28 | import { Root } from './components/Root';
29 |
30 | import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
31 | import { createApp } from '@backstage/app-defaults';
32 | import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
33 | import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
34 | import { RequirePermission } from '@backstage/plugin-permission-react';
35 | import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
36 | /* ignore lint error for internal dependencies */
37 | /* eslint-disable */
38 | import { EntityTektonPipelinesContent } from '@jquad-group/backstage-plugin-tekton-pipelines-plugin';
39 |
40 | const app = createApp({
41 | apis,
42 | bindRoutes({ bind }) {
43 | bind(catalogPlugin.externalRoutes, {
44 | createComponent: scaffolderPlugin.routes.root,
45 | viewTechDoc: techdocsPlugin.routes.docRoot,
46 | createFromTemplate: scaffolderPlugin.routes.selectedTemplate,
47 | });
48 | bind(apiDocsPlugin.externalRoutes, {
49 | registerApi: catalogImportPlugin.routes.importPage,
50 | });
51 | bind(scaffolderPlugin.externalRoutes, {
52 | registerComponent: catalogImportPlugin.routes.importPage,
53 | viewTechDoc: techdocsPlugin.routes.docRoot,
54 | });
55 | bind(orgPlugin.externalRoutes, {
56 | catalogIndex: catalogPlugin.routes.catalogIndex,
57 | });
58 | },
59 | });
60 |
61 | const routes = (
62 |
63 | } />
64 | } />
65 | }
68 | >
69 | {entityPage}
70 |
71 | } />
72 | }
75 | >
76 |
77 |
78 |
79 |
80 | } />
81 | } />
82 | }
85 | />
86 |
90 |
91 |
92 | }
93 | />
94 | }>
95 | {searchPage}
96 |
97 | } />
98 | } />
99 | } />
100 |
101 | );
102 |
103 | export default app.createRoot(
104 | <>
105 |
106 |
107 |
108 | {routes}
109 |
110 | >,
111 | );
112 |
--------------------------------------------------------------------------------
/packages/app/src/apis.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ScmIntegrationsApi,
3 | scmIntegrationsApiRef,
4 | ScmAuth,
5 | } from '@backstage/integration-react';
6 | import {
7 | AnyApiFactory,
8 | configApiRef,
9 | createApiFactory,
10 | } from '@backstage/core-plugin-api';
11 |
12 | export const apis: AnyApiFactory[] = [
13 | createApiFactory({
14 | api: scmIntegrationsApiRef,
15 | deps: { configApi: configApiRef },
16 | factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
17 | }),
18 | ScmAuth.createDefaultApiFactory(),
19 | ];
20 |
--------------------------------------------------------------------------------
/packages/app/src/components/Root/LogoFull.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core';
3 |
4 | const useStyles = makeStyles({
5 | svg: {
6 | width: 'auto',
7 | height: 30,
8 | },
9 | path: {
10 | fill: '#7df3e1',
11 | },
12 | });
13 | const LogoFull = () => {
14 | const classes = useStyles();
15 |
16 | return (
17 |
27 | );
28 | };
29 |
30 | export default LogoFull;
31 |
--------------------------------------------------------------------------------
/packages/app/src/components/Root/LogoIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core';
3 |
4 | const useStyles = makeStyles({
5 | svg: {
6 | width: 'auto',
7 | height: 28,
8 | },
9 | path: {
10 | fill: '#7df3e1',
11 | },
12 | });
13 |
14 | const LogoIcon = () => {
15 | const classes = useStyles();
16 |
17 | return (
18 |
28 | );
29 | };
30 |
31 | export default LogoIcon;
32 |
--------------------------------------------------------------------------------
/packages/app/src/components/Root/Root.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren } from 'react';
2 | import { makeStyles } from '@material-ui/core';
3 | import HomeIcon from '@material-ui/icons/Home';
4 | import ExtensionIcon from '@material-ui/icons/Extension';
5 | import MapIcon from '@material-ui/icons/MyLocation';
6 | import LibraryBooks from '@material-ui/icons/LibraryBooks';
7 | import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
8 | import LogoFull from './LogoFull';
9 | import LogoIcon from './LogoIcon';
10 | import {
11 | Settings as SidebarSettings,
12 | UserSettingsSignInAvatar,
13 | } from '@backstage/plugin-user-settings';
14 | import { SidebarSearchModal } from '@backstage/plugin-search';
15 | import {
16 | Sidebar,
17 | sidebarConfig,
18 | SidebarDivider,
19 | SidebarGroup,
20 | SidebarItem,
21 | SidebarPage,
22 | SidebarScrollWrapper,
23 | SidebarSpace,
24 | useSidebarOpenState,
25 | Link,
26 | } from '@backstage/core-components';
27 | import MenuIcon from '@material-ui/icons/Menu';
28 | import SearchIcon from '@material-ui/icons/Search';
29 |
30 | const useSidebarLogoStyles = makeStyles({
31 | root: {
32 | width: sidebarConfig.drawerWidthClosed,
33 | height: 3 * sidebarConfig.logoHeight,
34 | display: 'flex',
35 | flexFlow: 'row nowrap',
36 | alignItems: 'center',
37 | marginBottom: -14,
38 | },
39 | link: {
40 | width: sidebarConfig.drawerWidthClosed,
41 | marginLeft: 24,
42 | },
43 | });
44 |
45 | const SidebarLogo = () => {
46 | const classes = useSidebarLogoStyles();
47 | const { isOpen } = useSidebarOpenState();
48 |
49 | return (
50 |
51 |
52 | {isOpen ? : }
53 |
54 |
55 | );
56 | };
57 |
58 | export const Root = ({ children }: PropsWithChildren<{}>) => (
59 |
60 |
61 |
62 | } to="/search">
63 |
64 |
65 |
66 | }>
67 | {/* Global nav, not org-specific */}
68 |
69 |
70 |
71 |
72 | {/* End global nav */}
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | }
83 | to="/settings"
84 | >
85 |
86 |
87 |
88 | {children}
89 |
90 | );
91 |
--------------------------------------------------------------------------------
/packages/app/src/components/Root/index.ts:
--------------------------------------------------------------------------------
1 | export { Root } from './Root';
2 |
--------------------------------------------------------------------------------
/packages/app/src/components/catalog/EntityPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Grid } from '@material-ui/core';
3 | import {
4 | EntityApiDefinitionCard,
5 | EntityConsumedApisCard,
6 | EntityConsumingComponentsCard,
7 | EntityHasApisCard,
8 | EntityProvidedApisCard,
9 | EntityProvidingComponentsCard,
10 | } from '@backstage/plugin-api-docs';
11 | import {
12 | EntityAboutCard,
13 | EntityDependsOnComponentsCard,
14 | EntityDependsOnResourcesCard,
15 | EntityHasComponentsCard,
16 | EntityHasResourcesCard,
17 | EntityHasSubcomponentsCard,
18 | EntityHasSystemsCard,
19 | EntityLayout,
20 | EntityLinksCard,
21 | EntitySwitch,
22 | EntityOrphanWarning,
23 | EntityProcessingErrorsPanel,
24 | isComponentType,
25 | isKind,
26 | hasCatalogProcessingErrors,
27 | isOrphan,
28 | hasRelationWarnings,
29 | EntityRelationWarning,
30 | } from '@backstage/plugin-catalog';
31 | import {
32 | isGithubActionsAvailable,
33 | EntityGithubActionsContent,
34 | } from '@backstage/plugin-github-actions';
35 | import {
36 | EntityUserProfileCard,
37 | EntityGroupProfileCard,
38 | EntityMembersListCard,
39 | EntityOwnershipCard,
40 | } from '@backstage/plugin-org';
41 | import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
42 | import { EmptyState } from '@backstage/core-components';
43 | import {
44 | Direction,
45 | EntityCatalogGraphCard,
46 | } from '@backstage/plugin-catalog-graph';
47 | import {
48 | RELATION_API_CONSUMED_BY,
49 | RELATION_API_PROVIDED_BY,
50 | RELATION_CONSUMES_API,
51 | RELATION_DEPENDENCY_OF,
52 | RELATION_DEPENDS_ON,
53 | RELATION_HAS_PART,
54 | RELATION_PART_OF,
55 | RELATION_PROVIDES_API,
56 | } from '@backstage/catalog-model';
57 |
58 | import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
59 | import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
60 | /* ignore lint error for internal dependencies */
61 | /* eslint-disable */
62 | import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
63 | import { EntityTektonPipelinesContent, isTektonCiAvailable } from '@jquad-group/backstage-plugin-tekton-pipelines-plugin';
64 |
65 | const techdocsContent = (
66 |
67 |
68 |
69 |
70 |
71 | );
72 |
73 | const cicdContent = (
74 | // This is an example of how you can implement your company's logic in entity page.
75 | // You can for example enforce that all components of type 'service' should use GitHubActions
76 |
77 |
78 |
79 |
80 |
81 |
82 |
92 | Read more
93 |
94 | }
95 | />
96 |
97 |
98 | );
99 |
100 | const entityWarningContent = (
101 | <>
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | >
126 | );
127 |
128 | const overviewContent = (
129 |
130 | {entityWarningContent}
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | );
146 |
147 | const serviceEntityPage = (
148 |
149 |
150 | {overviewContent}
151 |
152 |
153 |
154 | {cicdContent}
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | {techdocsContent}
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | Boolean(isTektonCiAvailable(e))}>
190 |
191 |
192 |
193 |
194 |
199 |
200 |
201 |
202 |
203 |
204 | );
205 |
206 | const websiteEntityPage = (
207 |
208 |
209 | {overviewContent}
210 |
211 |
212 |
213 | {cicdContent}
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 | {techdocsContent}
229 |
230 |
231 | );
232 |
233 | /**
234 | * NOTE: This page is designed to work on small screens such as mobile devices.
235 | * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`,
236 | * since this does not default. If no breakpoints are used, the items will equitably share the available space.
237 | * https://material-ui.com/components/grid/#basic-grid.
238 | */
239 |
240 | const defaultEntityPage = (
241 |
242 |
243 | {overviewContent}
244 |
245 |
246 |
247 | {techdocsContent}
248 |
249 |
250 | );
251 |
252 | const componentPage = (
253 |
254 |
255 | {serviceEntityPage}
256 |
257 |
258 |
259 | {websiteEntityPage}
260 |
261 |
262 | {defaultEntityPage}
263 |
264 | );
265 |
266 | const apiPage = (
267 |
268 |
269 |
270 | {entityWarningContent}
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 | );
300 |
301 | const userPage = (
302 |
303 |
304 |
305 | {entityWarningContent}
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 | );
316 |
317 | const groupPage = (
318 |
319 |
320 |
321 | {entityWarningContent}
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 | );
338 |
339 | const systemPage = (
340 |
341 |
342 |
343 | {entityWarningContent}
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
382 |
383 |
384 | );
385 |
386 | const domainPage = (
387 |
388 |
389 |
390 | {entityWarningContent}
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 | );
404 |
405 | export const entityPage = (
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 | {defaultEntityPage}
415 |
416 | );
417 |
--------------------------------------------------------------------------------
/packages/app/src/components/search/SearchPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles, Theme, Grid, Paper } from '@material-ui/core';
3 |
4 | import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
5 | import {
6 | catalogApiRef,
7 | CATALOG_FILTER_EXISTS,
8 | } from '@backstage/plugin-catalog-react';
9 | import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
10 |
11 | import { SearchType } from '@backstage/plugin-search';
12 | import {
13 | SearchBar,
14 | SearchFilter,
15 | SearchResult,
16 | SearchPagination,
17 | useSearch,
18 | } from '@backstage/plugin-search-react';
19 | import {
20 | CatalogIcon,
21 | Content,
22 | DocsIcon,
23 | Header,
24 | Page,
25 | } from '@backstage/core-components';
26 | import { useApi } from '@backstage/core-plugin-api';
27 |
28 | const useStyles = makeStyles((theme: Theme) => ({
29 | bar: {
30 | padding: theme.spacing(1, 0),
31 | },
32 | filters: {
33 | padding: theme.spacing(2),
34 | marginTop: theme.spacing(2),
35 | },
36 | filter: {
37 | '& + &': {
38 | marginTop: theme.spacing(2.5),
39 | },
40 | },
41 | }));
42 |
43 | const SearchPage = () => {
44 | const classes = useStyles();
45 | const { types } = useSearch();
46 | const catalogApi = useApi(catalogApiRef);
47 |
48 | return (
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ,
67 | },
68 | {
69 | value: 'techdocs',
70 | name: 'Documentation',
71 | icon: ,
72 | },
73 | ]}
74 | />
75 |
76 | {types.includes('techdocs') && (
77 | {
82 | // Return a list of entities which are documented.
83 | const { items } = await catalogApi.getEntities({
84 | fields: ['metadata.name'],
85 | filter: {
86 | 'metadata.annotations.backstage.io/techdocs-ref':
87 | CATALOG_FILTER_EXISTS,
88 | },
89 | });
90 |
91 | const names = items.map(entity => entity.metadata.name);
92 | names.sort();
93 | return names;
94 | }}
95 | />
96 | )}
97 |
103 |
109 |
110 |
111 |
112 |
113 |
114 | } />
115 | } />
116 |
117 |
118 |
119 |
120 |
121 | );
122 | };
123 |
124 | export const searchPage = ;
125 |
--------------------------------------------------------------------------------
/packages/app/src/index.tsx:
--------------------------------------------------------------------------------
1 | import '@backstage/cli/asset-types';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom/client';
4 | import App from './App';
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render();
7 |
--------------------------------------------------------------------------------
/packages/app/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 |
--------------------------------------------------------------------------------
/packages/backend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
2 |
--------------------------------------------------------------------------------
/packages/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | # This dockerfile builds an image for the backend package.
2 | # It should be executed with the root of the repo as docker context.
3 | #
4 | # Before building this image, be sure to have run the following commands in the repo root:
5 | #
6 | # yarn install
7 | # yarn tsc
8 | # yarn build:backend
9 | #
10 | # Once the commands have been run, you can build the image using `yarn build-image`
11 |
12 | FROM node:18-bookworm-slim
13 |
14 | # Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
15 | RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
16 | --mount=type=cache,target=/var/lib/apt,sharing=locked \
17 | apt-get update && \
18 | apt-get install -y --no-install-recommends python3 g++ build-essential && \
19 | yarn config set python /usr/bin/python3
20 |
21 | # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
22 | # in which case you should also move better-sqlite3 to "devDependencies" in package.json.
23 | RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
24 | --mount=type=cache,target=/var/lib/apt,sharing=locked \
25 | apt-get update && \
26 | apt-get install -y --no-install-recommends libsqlite3-dev
27 |
28 | # From here on we use the least-privileged `node` user to run the backend.
29 | USER node
30 |
31 | # This should create the app dir as `node`.
32 | # If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`.
33 | # If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`.
34 | WORKDIR /app
35 |
36 | # This switches many Node.js dependencies to production mode.
37 | ENV NODE_ENV production
38 |
39 | # Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
40 | # The skeleton contains the package.json of each package in the monorepo,
41 | # and along with yarn.lock and the root package.json, that's enough to run yarn install.
42 | COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
43 | RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
44 |
45 | RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
46 | yarn install --frozen-lockfile --production --network-timeout 300000
47 |
48 | # Then copy the rest of the backend bundle, along with any other files we might want.
49 | COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./
50 | RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
51 |
52 | CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]
53 |
--------------------------------------------------------------------------------
/packages/backend/README.md:
--------------------------------------------------------------------------------
1 | # example-backend
2 |
3 | This package is an EXAMPLE of a Backstage backend.
4 |
5 | The main purpose of this package is to provide a test bed for Backstage plugins
6 | that have a backend part. Feel free to experiment locally or within your fork by
7 | adding dependencies and routes to this backend, to try things out.
8 |
9 | Our goal is to eventually amend the create-app flow of the CLI, such that a
10 | production ready version of a backend skeleton is made alongside the frontend
11 | app. Until then, feel free to experiment here!
12 |
13 | ## Development
14 |
15 | To run the example backend, first go to the project root and run
16 |
17 | ```bash
18 | yarn install
19 | ```
20 |
21 | You should only need to do this once.
22 |
23 | After that, go to the `packages/backend` directory and run
24 |
25 | ```bash
26 | yarn start
27 | ```
28 |
29 | If you want to override any configuration locally, for example adding any secrets,
30 | you can do so in `app-config.local.yaml`.
31 |
32 | The backend starts up on port 7007 per default.
33 |
34 | ## Populating The Catalog
35 |
36 | If you want to use the catalog functionality, you need to add so called
37 | locations to the backend. These are places where the backend can find some
38 | entity descriptor data to consume and serve. For more information, see
39 | [Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog).
40 |
41 | To get started quickly, this template already includes some statically configured example locations
42 | in `app-config.yaml` under `catalog.locations`. You can remove and replace these locations as you
43 | like, and also override them for local development in `app-config.local.yaml`.
44 |
45 | ## Authentication
46 |
47 | We chose [Passport](http://www.passportjs.org/) as authentication platform due
48 | to its comprehensive set of supported authentication
49 | [strategies](http://www.passportjs.org/packages/).
50 |
51 | Read more about the
52 | [auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md)
53 | and
54 | [how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md)
55 |
56 | ## Documentation
57 |
58 | - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
59 | - [Backstage Documentation](https://backstage.io/docs)
60 |
--------------------------------------------------------------------------------
/packages/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "0.0.0",
4 | "main": "dist/index.cjs.js",
5 | "types": "src/index.ts",
6 | "private": true,
7 | "backstage": {
8 | "role": "backend"
9 | },
10 | "scripts": {
11 | "start": "backstage-cli package start",
12 | "build": "backstage-cli package build",
13 | "lint": "backstage-cli package lint",
14 | "test": "backstage-cli package test",
15 | "clean": "backstage-cli package clean",
16 | "build-image": "docker build ../.. -f Dockerfile --tag backstage"
17 | },
18 | "dependencies": {
19 | "@backstage/backend-common": "^0.20.1",
20 | "@backstage/backend-tasks": "^0.5.14",
21 | "@backstage/catalog-client": "^1.5.2",
22 | "@backstage/catalog-model": "^1.4.3",
23 | "@backstage/config": "^1.1.1",
24 | "@backstage/plugin-app-backend": "^0.3.57",
25 | "@backstage/plugin-auth-backend": "^0.20.3",
26 | "@backstage/plugin-auth-node": "^0.4.3",
27 | "@backstage/plugin-catalog-backend": "^1.16.1",
28 | "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.6",
29 | "@backstage/plugin-kubernetes-backend": "^0.14.1",
30 | "@backstage/plugin-permission-common": "^0.7.12",
31 | "@backstage/plugin-permission-node": "^0.7.20",
32 | "@backstage/plugin-proxy-backend": "^0.4.7",
33 | "@backstage/plugin-scaffolder-backend": "^1.20.0",
34 | "@backstage/plugin-search-backend": "^1.4.9",
35 | "@backstage/plugin-search-backend-module-catalog": "^0.1.13",
36 | "@backstage/plugin-search-backend-module-pg": "^0.5.18",
37 | "@backstage/plugin-search-backend-module-techdocs": "^0.1.13",
38 | "@backstage/plugin-search-backend-node": "^1.2.13",
39 | "@backstage/plugin-techdocs-backend": "^1.9.2",
40 | "app": "link:../app",
41 | "better-sqlite3": "^9.0.0",
42 | "dockerode": "^3.3.1",
43 | "express": "^4.17.1",
44 | "express-promise-router": "^4.1.0",
45 | "node-gyp": "^9.0.0",
46 | "pg": "^8.11.3",
47 | "winston": "^3.2.1"
48 | },
49 | "devDependencies": {
50 | "@backstage/cli": "^0.25.1",
51 | "@types/dockerode": "^3.3.0",
52 | "@types/express": "^4.17.6",
53 | "@types/express-serve-static-core": "^4.17.5",
54 | "@types/luxon": "^2.0.4"
55 | },
56 | "files": [
57 | "dist"
58 | ]
59 | }
60 |
--------------------------------------------------------------------------------
/packages/backend/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { PluginEnvironment } from './types';
2 |
3 | describe('test', () => {
4 | it('unbreaks the test runner', () => {
5 | const unbreaker = {} as PluginEnvironment;
6 | expect(unbreaker).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/packages/backend/src/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Hi!
3 | *
4 | * Note that this is an EXAMPLE Backstage backend. Please check the README.
5 | *
6 | * Happy hacking!
7 | */
8 |
9 | import Router from 'express-promise-router';
10 | import {
11 | createServiceBuilder,
12 | loadBackendConfig,
13 | getRootLogger,
14 | useHotMemoize,
15 | notFoundHandler,
16 | CacheManager,
17 | DatabaseManager,
18 | HostDiscovery,
19 | UrlReaders,
20 | ServerTokenManager,
21 | } from '@backstage/backend-common';
22 | import { TaskScheduler } from '@backstage/backend-tasks';
23 | import { Config } from '@backstage/config';
24 | import app from './plugins/app';
25 | import auth from './plugins/auth';
26 | import catalog from './plugins/catalog';
27 | import scaffolder from './plugins/scaffolder';
28 | import proxy from './plugins/proxy';
29 | import techdocs from './plugins/techdocs';
30 | import search from './plugins/search';
31 | import { PluginEnvironment } from './types';
32 | import { ServerPermissionClient } from '@backstage/plugin-permission-node';
33 | import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
34 | import kubernetes from './plugins/kubernetes';
35 |
36 | function makeCreateEnv(config: Config) {
37 | const root = getRootLogger();
38 | const reader = UrlReaders.default({ logger: root, config });
39 | const discovery = HostDiscovery.fromConfig(config);
40 | const cacheManager = CacheManager.fromConfig(config);
41 | const databaseManager = DatabaseManager.fromConfig(config, { logger: root });
42 | const tokenManager = ServerTokenManager.noop();
43 | const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager });
44 |
45 | const identity = DefaultIdentityClient.create({
46 | discovery,
47 | });
48 | const permissions = ServerPermissionClient.fromConfig(config, {
49 | discovery,
50 | tokenManager,
51 | });
52 |
53 | root.info(`Created UrlReader ${reader}`);
54 |
55 | return (plugin: string): PluginEnvironment => {
56 | const logger = root.child({ type: 'plugin', plugin });
57 | const database = databaseManager.forPlugin(plugin);
58 | const cache = cacheManager.forPlugin(plugin);
59 | const scheduler = taskScheduler.forPlugin(plugin);
60 | return {
61 | logger,
62 | database,
63 | cache,
64 | config,
65 | reader,
66 | discovery,
67 | tokenManager,
68 | scheduler,
69 | permissions,
70 | identity,
71 | };
72 | };
73 | }
74 |
75 | async function main() {
76 | const config = await loadBackendConfig({
77 | argv: process.argv,
78 | logger: getRootLogger(),
79 | });
80 | const createEnv = makeCreateEnv(config);
81 |
82 | const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
83 | const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
84 | const authEnv = useHotMemoize(module, () => createEnv('auth'));
85 | const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
86 | const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
87 | const searchEnv = useHotMemoize(module, () => createEnv('search'));
88 | const appEnv = useHotMemoize(module, () => createEnv('app'));
89 | const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
90 |
91 | const apiRouter = Router();
92 | apiRouter.use('/catalog', await catalog(catalogEnv));
93 | apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
94 | apiRouter.use('/auth', await auth(authEnv));
95 | apiRouter.use('/techdocs', await techdocs(techdocsEnv));
96 | apiRouter.use('/proxy', await proxy(proxyEnv));
97 | apiRouter.use('/search', await search(searchEnv));
98 | apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
99 |
100 | // Add backends ABOVE this line; this 404 handler is the catch-all fallback
101 | apiRouter.use(notFoundHandler());
102 |
103 | const service = createServiceBuilder(module)
104 | .loadConfig(config)
105 | .addRouter('/api', apiRouter)
106 | .addRouter('', await app(appEnv));
107 |
108 | await service.start().catch(err => {
109 | console.log(err);
110 | process.exit(1);
111 | });
112 | }
113 |
114 | module.hot?.accept();
115 | main().catch(error => {
116 | console.error('Backend failed to start up', error);
117 | process.exit(1);
118 | });
119 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/app.ts:
--------------------------------------------------------------------------------
1 | import { createRouter } from '@backstage/plugin-app-backend';
2 | import { Router } from 'express';
3 | import { PluginEnvironment } from '../types';
4 |
5 | export default async function createPlugin(
6 | env: PluginEnvironment,
7 | ): Promise {
8 | return await createRouter({
9 | logger: env.logger,
10 | config: env.config,
11 | database: env.database,
12 | appPackageName: 'app',
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/auth.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createRouter,
3 | providers,
4 | defaultAuthProviderFactories,
5 | } from '@backstage/plugin-auth-backend';
6 | import { Router } from 'express';
7 | import { PluginEnvironment } from '../types';
8 |
9 | export default async function createPlugin(
10 | env: PluginEnvironment,
11 | ): Promise {
12 | return await createRouter({
13 | logger: env.logger,
14 | config: env.config,
15 | database: env.database,
16 | discovery: env.discovery,
17 | tokenManager: env.tokenManager,
18 | providerFactories: {
19 | ...defaultAuthProviderFactories,
20 |
21 | // This replaces the default GitHub auth provider with a customized one.
22 | // The `signIn` option enables sign-in for this provider, using the
23 | // identity resolution logic that's provided in the `resolver` callback.
24 | //
25 | // This particular resolver makes all users share a single "guest" identity.
26 | // It should only be used for testing and trying out Backstage.
27 | //
28 | // If you want to use a production ready resolver you can switch to
29 | // the one that is commented out below, it looks up a user entity in the
30 | // catalog using the GitHub username of the authenticated user.
31 | // That resolver requires you to have user entities populated in the catalog,
32 | // for example using https://backstage.io/docs/integrations/github/org
33 | //
34 | // There are other resolvers to choose from, and you can also create
35 | // your own, see the auth documentation for more details:
36 | //
37 | // https://backstage.io/docs/auth/identity-resolver
38 | github: providers.github.create({
39 | signIn: {
40 | resolver(_, ctx) {
41 | const userRef = 'user:default/guest'; // Must be a full entity reference
42 | return ctx.issueToken({
43 | claims: {
44 | sub: userRef, // The user's own identity
45 | ent: [userRef], // A list of identities that the user claims ownership through
46 | },
47 | });
48 | },
49 | // resolver: providers.github.resolvers.usernameMatchingUserEntityName(),
50 | },
51 | }),
52 | },
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/catalog.ts:
--------------------------------------------------------------------------------
1 | import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
2 | import { ScaffolderEntitiesProcessor } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model';
3 | import { Router } from 'express';
4 | import { PluginEnvironment } from '../types';
5 |
6 | export default async function createPlugin(
7 | env: PluginEnvironment,
8 | ): Promise {
9 | const builder = await CatalogBuilder.create(env);
10 | builder.addProcessor(new ScaffolderEntitiesProcessor());
11 | const { processingEngine, router } = await builder.build();
12 | await processingEngine.start();
13 | return router;
14 | }
15 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/kubernetes.ts:
--------------------------------------------------------------------------------
1 | import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
2 | import { Router } from 'express';
3 | import { PluginEnvironment } from '../types';
4 | import { CatalogClient } from '@backstage/catalog-client';
5 |
6 | export default async function createPlugin(
7 | env: PluginEnvironment,
8 | ): Promise {
9 | const catalogApi = new CatalogClient({ discoveryApi: env.discovery });
10 | const { router } = await KubernetesBuilder.createBuilder({
11 | logger: env.logger,
12 | config: env.config,
13 | catalogApi,
14 | permissions: env.permissions,
15 | }).build();
16 | return router;
17 | }
--------------------------------------------------------------------------------
/packages/backend/src/plugins/proxy.ts:
--------------------------------------------------------------------------------
1 | import { createRouter } from '@backstage/plugin-proxy-backend';
2 | import { Router } from 'express';
3 | import { PluginEnvironment } from '../types';
4 |
5 | export default async function createPlugin(
6 | env: PluginEnvironment,
7 | ): Promise {
8 | return await createRouter({
9 | logger: env.logger,
10 | config: env.config,
11 | discovery: env.discovery,
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/scaffolder.ts:
--------------------------------------------------------------------------------
1 | import { CatalogClient } from '@backstage/catalog-client';
2 | import { createRouter } from '@backstage/plugin-scaffolder-backend';
3 | import { Router } from 'express';
4 | import type { PluginEnvironment } from '../types';
5 |
6 | export default async function createPlugin(
7 | env: PluginEnvironment,
8 | ): Promise {
9 | const catalogClient = new CatalogClient({
10 | discoveryApi: env.discovery,
11 | });
12 |
13 | return await createRouter({
14 | logger: env.logger,
15 | config: env.config,
16 | database: env.database,
17 | reader: env.reader,
18 | catalogClient,
19 | identity: env.identity,
20 | permissions: env.permissions,
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/search.ts:
--------------------------------------------------------------------------------
1 | import { useHotCleanup } from '@backstage/backend-common';
2 | import { createRouter } from '@backstage/plugin-search-backend';
3 | import {
4 | IndexBuilder,
5 | LunrSearchEngine,
6 | } from '@backstage/plugin-search-backend-node';
7 | import { PluginEnvironment } from '../types';
8 | import { DefaultCatalogCollatorFactory } from '@backstage/plugin-search-backend-module-catalog';
9 | import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs';
10 | import { Router } from 'express';
11 |
12 | export default async function createPlugin(
13 | env: PluginEnvironment,
14 | ): Promise {
15 | // Initialize a connection to a search engine.
16 | const searchEngine = new LunrSearchEngine({
17 | logger: env.logger,
18 | });
19 | const indexBuilder = new IndexBuilder({
20 | logger: env.logger,
21 | searchEngine,
22 | });
23 |
24 | const schedule = env.scheduler.createScheduledTaskRunner({
25 | frequency: { minutes: 10 },
26 | timeout: { minutes: 15 },
27 | // A 3 second delay gives the backend server a chance to initialize before
28 | // any collators are executed, which may attempt requests against the API.
29 | initialDelay: { seconds: 3 },
30 | });
31 |
32 | // Collators are responsible for gathering documents known to plugins. This
33 | // collator gathers entities from the software catalog.
34 | indexBuilder.addCollator({
35 | schedule,
36 | factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
37 | discovery: env.discovery,
38 | tokenManager: env.tokenManager,
39 | }),
40 | });
41 |
42 | // collator gathers entities from techdocs.
43 | indexBuilder.addCollator({
44 | schedule,
45 | factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
46 | discovery: env.discovery,
47 | logger: env.logger,
48 | tokenManager: env.tokenManager,
49 | }),
50 | });
51 |
52 | // The scheduler controls when documents are gathered from collators and sent
53 | // to the search engine for indexing.
54 | const { scheduler } = await indexBuilder.build();
55 | scheduler.start();
56 |
57 | useHotCleanup(module, () => scheduler.stop());
58 |
59 | return await createRouter({
60 | engine: indexBuilder.getSearchEngine(),
61 | types: indexBuilder.getDocumentTypes(),
62 | permissions: env.permissions,
63 | config: env.config,
64 | logger: env.logger,
65 | });
66 | }
67 |
--------------------------------------------------------------------------------
/packages/backend/src/plugins/techdocs.ts:
--------------------------------------------------------------------------------
1 | import { DockerContainerRunner } from '@backstage/backend-common';
2 | import {
3 | createRouter,
4 | Generators,
5 | Preparers,
6 | Publisher,
7 | } from '@backstage/plugin-techdocs-backend';
8 | import Docker from 'dockerode';
9 | import { Router } from 'express';
10 | import { PluginEnvironment } from '../types';
11 |
12 | export default async function createPlugin(
13 | env: PluginEnvironment,
14 | ): Promise {
15 | // Preparers are responsible for fetching source files for documentation.
16 | const preparers = await Preparers.fromConfig(env.config, {
17 | logger: env.logger,
18 | reader: env.reader,
19 | });
20 |
21 | // Docker client (conditionally) used by the generators, based on techdocs.generators config.
22 | const dockerClient = new Docker();
23 | const containerRunner = new DockerContainerRunner({ dockerClient });
24 |
25 | // Generators are used for generating documentation sites.
26 | const generators = await Generators.fromConfig(env.config, {
27 | logger: env.logger,
28 | containerRunner,
29 | });
30 |
31 | // Publisher is used for
32 | // 1. Publishing generated files to storage
33 | // 2. Fetching files from storage and passing them to TechDocs frontend.
34 | const publisher = await Publisher.fromConfig(env.config, {
35 | logger: env.logger,
36 | discovery: env.discovery,
37 | });
38 |
39 | // checks if the publisher is working and logs the result
40 | await publisher.getReadiness();
41 |
42 | return await createRouter({
43 | preparers,
44 | generators,
45 | publisher,
46 | logger: env.logger,
47 | config: env.config,
48 | discovery: env.discovery,
49 | cache: env.cache,
50 | });
51 | }
52 |
--------------------------------------------------------------------------------
/packages/backend/src/types.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from 'winston';
2 | import { Config } from '@backstage/config';
3 | import {
4 | PluginCacheManager,
5 | PluginDatabaseManager,
6 | PluginEndpointDiscovery,
7 | TokenManager,
8 | UrlReader,
9 | } from '@backstage/backend-common';
10 | import { PluginTaskScheduler } from '@backstage/backend-tasks';
11 | import { PermissionEvaluator } from '@backstage/plugin-permission-common';
12 | import { IdentityApi } from '@backstage/plugin-auth-node';
13 |
14 | export type PluginEnvironment = {
15 | logger: Logger;
16 | database: PluginDatabaseManager;
17 | cache: PluginCacheManager;
18 | config: Config;
19 | reader: UrlReader;
20 | discovery: PluginEndpointDiscovery;
21 | tokenManager: TokenManager;
22 | scheduler: PluginTaskScheduler;
23 | permissions: PermissionEvaluator;
24 | identity: IdentityApi;
25 | };
26 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 The Backstage Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { defineConfig } from '@playwright/test';
18 | import { generateProjects } from '@backstage/e2e-test-utils/playwright';
19 |
20 | /**
21 | * See https://playwright.dev/docs/test-configuration.
22 | */
23 | export default defineConfig({
24 | timeout: 60_000,
25 |
26 | expect: {
27 | timeout: 5_000,
28 | },
29 |
30 | // Run your local dev server before starting the tests
31 | webServer: process.env.CI
32 | ? []
33 | : [
34 | {
35 | command: 'yarn start',
36 | port: 3000,
37 | reuseExistingServer: true,
38 | timeout: 60_000,
39 | },
40 | ],
41 |
42 | forbidOnly: !!process.env.CI,
43 |
44 | retries: process.env.CI ? 2 : 0,
45 |
46 | reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]],
47 |
48 | use: {
49 | actionTimeout: 0,
50 | baseURL:
51 | process.env.PLAYWRIGHT_URL ??
52 | (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'),
53 | screenshot: 'only-on-failure',
54 | trace: 'on-first-retry',
55 | },
56 |
57 | outputDir: 'node_modules/.cache/e2e-test-results',
58 |
59 | projects: generateProjects(), // Find all packages with e2e-test folders
60 | });
61 |
--------------------------------------------------------------------------------
/plugins/README.md:
--------------------------------------------------------------------------------
1 | # The Plugins Folder
2 |
3 | This is where your own plugins and their associated modules live, each in a
4 | separate folder of its own.
5 |
6 | If you want to create a new plugin here, go to your project root directory, run
7 | the command `yarn new`, and follow the on-screen instructions.
8 |
9 | You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)!
10 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ...require('@backstage/cli/config/eslint-factory')(__dirname),
3 | rules: {
4 | ...require('@backstage/cli/config/eslint-factory')(__dirname).rules,
5 | 'no-nested-ternary': 'off',
6 | }
7 | }
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/README.md:
--------------------------------------------------------------------------------
1 | # tekton-pipelines-plugin
2 |
3 | This is the Tekton Pipelines frontend plugin.
4 |
5 | _This plugin was created through the Backstage CLI_
6 |
7 | ## Getting started
8 |
9 | Access the frontend plugin by running `yarn start` in the root directory of the plugin, and then navigate to /tekton-pipelines.
10 |
11 | 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 directory.
12 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/dev/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createDevApp } from '@backstage/dev-utils';
3 | import { EntityTektonPipelinesContent, tektonPipelinesPluginPlugin } from '../src/plugin';
4 | import { KubernetesApi, kubernetesApiRef, kubernetesPlugin} from '@backstage/plugin-kubernetes';
5 | import {
6 | CustomObjectsByEntityRequest,
7 | FetchResponse,
8 | ObjectsByEntityResponse,
9 | WorkloadsByEntityRequest,
10 | } from '@backstage/plugin-kubernetes-common';
11 |
12 | import pipelineRun1 from '../src/__fixtures__/pipelineRun1.json';
13 | import pipelineRun2 from '../src/__fixtures__/pipelineRun2.json';
14 | import pipelineRun3 from '../src/__fixtures__/pipelineRun3.json';
15 | import { TestApiProvider } from '@backstage/test-utils';
16 | import { EntityProvider } from '@backstage/plugin-catalog-react';
17 | import { Entity } from '@backstage/catalog-model';
18 |
19 |
20 | const mockEntity: Entity = {
21 | apiVersion: 'backstage.io/v1alpha1',
22 | kind: 'Component',
23 | metadata: {
24 | name: 'backstage',
25 | description: 'backstage.io',
26 | annotations: {
27 | 'backstage.io/kubernetes-label-selector': 'app=microservice',
28 | 'tektonci/enabled': "true",
29 | },
30 | },
31 | spec: {
32 | lifecycle: 'production',
33 | type: 'service',
34 | owner: 'user:guest',
35 | },
36 | };
37 |
38 | class MockKubernetesClient implements KubernetesApi {
39 | readonly resources: FetchResponse[];
40 |
41 | constructor(fixtureData: { [resourceType: string]: any[] }) {
42 | this.resources = Object.entries(fixtureData).flatMap(
43 | ([type, resources]) =>
44 | ({ type: type.toLocaleLowerCase('en-US'), resources } as FetchResponse),
45 | );
46 | }
47 | getCluster(_clusterName: string): Promise<{ name: string; authProvider: string; oidcTokenProvider?: string | undefined; dashboardUrl?: string | undefined; } | undefined> {
48 | throw new Error('Method not implemented.');
49 | }
50 | async getPodLogs(_request: {
51 | podName: string;
52 | namespace: string;
53 | clusterName: string;
54 | containerName: string;
55 | token: string;
56 | }): Promise {
57 | return await 'some logs';
58 | }
59 | async getWorkloadsByEntity(
60 | _request: WorkloadsByEntityRequest,
61 | ): Promise {
62 | return {
63 | items: [
64 | {
65 | cluster: { name: 'mock-cluster' },
66 | resources: this.resources,
67 | podMetrics: [],
68 | errors: [],
69 | },
70 | ],
71 | };
72 | }
73 | async getCustomObjectsByEntity(
74 | _request: CustomObjectsByEntityRequest,
75 | ): Promise {
76 | return {
77 | items: [
78 | {
79 | cluster: { name: 'mock-cluster' },
80 | resources: this.resources,
81 | podMetrics: [],
82 | errors: [],
83 | },
84 | ],
85 | };
86 | }
87 |
88 | async getObjectsByEntity(): Promise {
89 | return {
90 | items: [
91 | {
92 | cluster: { name: 'mock-cluster' },
93 | resources: this.resources,
94 | podMetrics: [],
95 | errors: [],
96 | },
97 | ],
98 | };
99 | }
100 |
101 | async getClusters(): Promise<{ name: string; authProvider: string }[]> {
102 | return [{ name: 'mock-cluster', authProvider: 'serviceAccount' }];
103 | }
104 |
105 | async proxy(_options: { clusterName: String; path: String }): Promise {
106 | return {
107 | kind: 'Namespace',
108 | apiVersion: 'v1',
109 | metadata: {
110 | name: 'mock-ns',
111 | },
112 | };
113 | }
114 | }
115 |
116 | createDevApp()
117 | .registerPlugin(kubernetesPlugin, tektonPipelinesPluginPlugin)
118 | .addPage({
119 | element: (
120 |
123 |
124 |
125 |
126 |
127 | ),
128 | title: 'Tekton Pipelines 1',
129 | path: '/tekton-pipelines-1'
130 | }).addPage({
131 | element: (
132 |
135 |
136 |
137 |
138 |
139 | ),
140 | title: 'Tekton Pipelines 2',
141 | path: '/tekton-pipelines-2'
142 | }).addPage({
143 | element: (
144 |
147 |
148 |
149 |
150 |
151 | ),
152 | title: 'Tekton Pipelines 3',
153 | path: '/tekton-pipelines-3'
154 | })
155 | .render();
156 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jquad-group/backstage-plugin-tekton-pipelines-plugin",
3 | "version": "1.1.0-beta.4",
4 | "main": "src/index.ts",
5 | "types": "src/index.ts",
6 | "license": "Apache-2.0",
7 | "private": false,
8 | "publishConfig": {
9 | "access": "public",
10 | "main": "dist/index.esm.js",
11 | "types": "dist/index.d.ts"
12 | },
13 | "backstage": {
14 | "role": "frontend-plugin"
15 | },
16 | "sideEffects": false,
17 | "scripts": {
18 | "start": "backstage-cli package start",
19 | "build": "backstage-cli package build",
20 | "lint": "backstage-cli package lint",
21 | "test": "backstage-cli package test",
22 | "clean": "backstage-cli package clean",
23 | "prepack": "backstage-cli package prepack",
24 | "postpack": "backstage-cli package postpack"
25 | },
26 | "dependencies": {
27 | "@backstage/catalog-model": "^1.4.3",
28 | "@backstage/core-components": "^0.13.10",
29 | "@backstage/core-plugin-api": "^1.8.2",
30 | "@backstage/plugin-catalog-react": "^1.9.3",
31 | "@backstage/plugin-kubernetes": "^0.11.4",
32 | "@backstage/theme": "^0.5.0",
33 | "@material-ui/core": "^4.9.13",
34 | "@material-ui/icons": "^4.9.1",
35 | "@material-ui/lab": "^4.0.0-alpha.57",
36 | "react-digraph": "^9.1.1",
37 | "react-use": "^17.2.4"
38 | },
39 | "peerDependencies": {
40 | "react": "^16.13.1 || ^17.0.0"
41 | },
42 | "devDependencies": {
43 | "@backstage/cli": "^0.25.1",
44 | "@backstage/core-app-api": "^1.11.3",
45 | "@backstage/dev-utils": "^1.0.26",
46 | "@backstage/plugin-kubernetes-common": "^0.7.3",
47 | "@backstage/test-utils": "^1.4.7",
48 | "@testing-library/jest-dom": "^5.10.1",
49 | "@testing-library/react": "^12.1.3",
50 | "@testing-library/user-event": "^14.0.0",
51 | "msw": "^1.0.0"
52 | },
53 | "files": [
54 | "dist"
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/Router.tsx:
--------------------------------------------------------------------------------
1 | import { Entity } from '@backstage/catalog-model';
2 | import {
3 | LinkButton,
4 | MissingAnnotationEmptyState,
5 | } from '@backstage/core-components';
6 | import { useEntity } from '@backstage/plugin-catalog-react';
7 | import React from 'react';
8 | import { Route, Routes } from 'react-router-dom';
9 | import { TektonDashboardComponent } from './components/TektonDashboard';
10 |
11 | export const TEKTON_PIPELINES_ANNOTATION = 'tektonci/enabled';
12 |
13 | export const isTektonCiAvailable = (entity: Entity) =>
14 | Boolean(entity?.metadata.annotations?.[TEKTON_PIPELINES_ANNOTATION]);
15 |
16 | export const Router = (props: { refreshIntervalMs?: number }) => {
17 | const { entity } = useEntity();
18 |
19 | const tektonPipelinesAnnotationValue =
20 | entity.metadata.annotations?.[TEKTON_PIPELINES_ANNOTATION];
21 |
22 | if (
23 | tektonPipelinesAnnotationValue
24 | ) {
25 | return (
26 |
27 |
28 |
35 | }
36 | />
37 |
38 |
39 | );
40 | }
41 |
42 | return (
43 | <>
44 |
47 |
48 |
54 | Read Tekton Pipelines Plugin Docs
55 |
56 | >
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/__fixtures__/log.json:
--------------------------------------------------------------------------------
1 | {
2 | "log": "Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content Skip to main content \n \n mkdir -p /workspace/repo/bin\n test -s /workspace/repo/bin/controller-gen || GOBIN=/workspace/repo/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.10.0\n go: downloading sigs.k8s.io/controller-tools v0.10.0\n go: downloading github.com/spf13/cobra v1.4.0\n go: downloading github.com/spf13/pflag v1.0.5\n go: downloading github.com/gobuffalo/flect v0.2.5\n go: downloading k8s.io/apiextensions-apiserver v0.25.0\n go: downloading k8s.io/apimachinery v0.25.0\n go: downloading golang.org/x/tools v0.1.12\n go: downloading gopkg.in/yaml.v2 v2.4.0\n go: downloading github.com/fatih/color v1.12.0\n go: downloading k8s.io/api v0.25.0\n go: downloading gopkg.in/yaml.v3 v3.0.1\n go: downloading sigs.k8s.io/yaml v1.3.0\n go: downloading github.com/gogo/protobuf v1.3.2\n go: downloading k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed\n go: downloading k8s.io/klog/v2 v2.70.1\n go: downloading sigs.k8s.io/structured-merge-diff/v4 v4.2.3\n go: downloading github.com/google/gofuzz v1.1.0\n go: downloading github.com/mattn/go-colorable v0.1.8\n go: downloading github.com/mattn/go-isatty v0.0.12\n go: downloading golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f\n go: downloading sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2\n go: downloading github.com/go-logr/logr v1.2.3\n go: downloading gopkg.in/inf.v0 v0.9.1\n go: downloading github.com/json-iterator/go v1.1.12\n go: downloading golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4\n go: downloading golang.org/x/net v0.0.0-20220722155237-a158d28d115b\n go: downloading github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd\n go: downloading github.com/modern-go/reflect2 v1.0.2\n go: downloading golang.org/x/text v0.3.7\n /workspace/repo/bin/controller-gen rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n /workspace/repo/bin/controller-gen object:headerFile=\"hack/boilerplate.go.txt\" paths\"./...\"\n go fmt ./...\n go vet ./...\n go: downloading github.com/onsi/ginkgo/v2 v2.5.1\n go: downloading github.com/onsi/gomega v1.24.0\n go: downloading github.com/onsi/ginkgo v1.16.5\n test -s /workspace/repo/bin/setup-envtest || GOBIN=/workspace/repo/bin go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest\n go: downloading sigs.k8s.io/controller-runtime v0.14.5\n go: downloading sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230307042619-c304e7ec2ee7\n go: downloading github.com/go-logr/logr v1.2.0\n go: downloading github.com/go-logr/zapr v1.2.0\n go: downloading github.com/spf13/afero v1.6.0\n go: downloading go.uber.org/zap v1.19.1\n go: downloading go.uber.org/atomic v1.7.0\n go: downloading go.uber.org/multierr v1.6.0\n KUBEBUILDER_ASSETS=\"/root/.local/share/kubebuilder-envtest/k8s/1.23.1-linux-amd64\" go test ./... -coverprofile cover.out\n ? github.com/jquad-group/pullrequest-operator [no test files]\n ? github.com/jquad-group/pullrequest-operator/api/v1alpha1 [no test files]\n ok github.com/jquad-group/pullrequest-operator/controllers 0.047s coverage: 0.0% of statements\n ? github.com/jquad-group/pullrequest-operator/pkg/git [no test files]\n \n Step completed successfully\n \n fix-gh-client-cz7jw\n Last updated 26 days ago\n CompletedTasks Completed: 4 (Failed: 0, Cancelled 0), Skipped: 1\n \n clone\n github-commit-status-pending\n build-and-test-task-go\n \n test\n \n build\n \n github-commit-status-error\n github-commit-status-success\n testCompleted\n Duration: 4m 58s\n"
3 | }
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/CollapsibleTable/CollapsibleTable.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, waitFor } from '@testing-library/react';
3 | import { wrapInTestApp } from '@backstage/test-utils';
4 | import { CollapsibleTable } from './CollapsibleTable';
5 | import * as pipelineRunFileMock from './__fixtures__/pipelinerun.json';
6 |
7 | describe('CollapsibleTable', () => {
8 | it('should render a pipelinerun', async () => {
9 | const { getByText, debug } = render(
10 | wrapInTestApp(
11 | ,
12 | ),
13 | );
14 |
15 | await waitFor(() => {
16 | debug();
17 | });
18 |
19 | expect(getByText('feature-added-catalog-info-xdjk9')).toBeInTheDocument();
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/CollapsibleTable/CollapsibleTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // eslint-disable-next-line no-restricted-imports
3 | import { Table, TableContainer, TableBody, TableRow, TableCell, TableHead, TablePagination, TableFooter } from '@material-ui/core';
4 | /* ignore lint error for internal dependencies */
5 | /* eslint-disable */
6 | import { PipelineRun } from '../../types';
7 | /* eslint-enable */
8 | import TablePaginationActions from '@material-ui/core/TablePagination/TablePaginationActions';
9 | import { CollapsibleTableRow } from '../CollapsibleTableRow';
10 |
11 | type PipelineRunProps = {
12 | clusterName?: string;
13 | pipelineruns?: PipelineRun[];
14 | };
15 |
16 | export const CollapsibleTable = ({ clusterName, pipelineruns }: PipelineRunProps) => {
17 | const [page, setPage] = React.useState(0);
18 | const [rowsPerPage, setRowsPerPage] = React.useState(5);
19 | let emptyRows: number;
20 | // Avoid a layout jump when reaching the last page with empty rows.
21 | if (pipelineruns !== undefined) {
22 | emptyRows =
23 | page > 0 ? Math.max(0, (1 + page) * rowsPerPage - pipelineruns.length) : 0;
24 | } else {
25 | emptyRows =
26 | page > 0 ? Math.max(0, (1 + page) * rowsPerPage) : 0;
27 | }
28 |
29 | const handleChangePage = (
30 | _event: React.MouseEvent | null,
31 | newPage: number,
32 | ) => {
33 | setPage(newPage);
34 | };
35 |
36 | const handleChangeRowsPerPage = (
37 | event: React.ChangeEvent,
38 | ) => {
39 | setRowsPerPage(parseInt(event.target.value, 10));
40 | setPage(0);
41 | };
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
49 | Name
50 | Namespace
51 | Status
52 | Start Time
53 | Completion Time
54 | Dashboard
55 |
56 |
57 |
58 | {(pipelineruns !== undefined) && (clusterName !== undefined) && (rowsPerPage > 0
59 | ? pipelineruns.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
60 | : pipelineruns
61 | ).map((pipelineRun) => (
62 |
63 | ))}
64 | {emptyRows > 0 && (
65 |
66 |
67 |
68 | )}
69 |
70 |
71 | { pipelineruns !== undefined &&
72 |
88 |
89 | }
90 |
91 |
92 |
93 | );
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/CollapsibleTable/__fixtures__/pipelinerun.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "tekton.dev/v1beta1",
3 | "kind": "PipelineRun",
4 | "metadata": {
5 | "creationTimestamp": "2022-10-25T18:41:36Z",
6 | "generateName": "feature-added-catalog-info-",
7 | "generation": 1,
8 | "labels": {
9 | "kustomize.toolkit.fluxcd.io/name": "build-pipeline",
10 | "kustomize.toolkit.fluxcd.io/namespace": "sample-go-application-build",
11 | "pipeline.jquad.rocks/pr.branch.commit": "ab43364d1c192228832068e93c0a1357412e48af",
12 | "pipeline.jquad.rocks/pr.branch.name": "feature-added-catalog-info",
13 | "tekton.dev/pipeline": "pullrequest-pipeline-go"
14 | },
15 | "name": "feature-added-catalog-info-xdjk9",
16 | "namespace": "sample-go-application-build",
17 | "ownerReferences": [
18 | {
19 | "apiVersion": "pipeline.jquad.rocks/v1alpha1",
20 | "blockOwnerDeletion": true,
21 | "controller": true,
22 | "kind": "PipelineTrigger",
23 | "name": "pipelinetrigger-for-sample-go-application",
24 | "uid": "81601ec0-c693-41c3-9605-4cfa5bf84f2f"
25 | }
26 | ],
27 | "resourceVersion": "142055452",
28 | "uid": "c8abf18e-ab08-4b09-af49-94c7d287b96a"
29 | },
30 | "spec": {
31 | "params": [
32 | {
33 | "name": "repositoryName",
34 | "value": "sample-go-application"
35 | },
36 | {
37 | "name": "pathToContext",
38 | "value": "/workspace/repo"
39 | },
40 | {
41 | "name": "commit",
42 | "value": ""
43 | },
44 | {
45 | "name": "branch-name",
46 | "value": "feature/added-catalog-info"
47 | },
48 | {
49 | "name": "repo-url",
50 | "value": "git@github.com:jquad-group/sample-go-application.git"
51 | },
52 | {
53 | "name": "repo-url-alternate",
54 | "value": "git@github.com:jquad-group/sample-go-application.git"
55 | },
56 | {
57 | "name": "gitrevision",
58 | "value": "feature/added-catalog-info"
59 | },
60 | {
61 | "name": "projectname",
62 | "value": "sample-go-application"
63 | },
64 | {
65 | "name": "statusUrl",
66 | "value": "https://api.github.com/repos/jquad-group/sample-go-application/statuses/ab43364d1c192228832068e93c0a1357412e48af"
67 | },
68 | {
69 | "name": "state",
70 | "value": "success"
71 | },
72 | {
73 | "name": "targetUrl",
74 | "value": "https://rancher.jquad.rocks/k8s/clusters/local/api/v1/namespaces/tekton-pipelines/services/http:tekton-dashboard:9097/proxy/#/pipelineruns"
75 | },
76 | {
77 | "name": "context",
78 | "value": "jquad-group/tekton-ci"
79 | },
80 | {
81 | "name": "githubSecretName",
82 | "value": "git-clone"
83 | },
84 | {
85 | "name": "githubUsernameKey",
86 | "value": "username"
87 | },
88 | {
89 | "name": "githubPasswordKey",
90 | "value": "password"
91 | }
92 | ],
93 | "pipelineRef": {
94 | "name": "pullrequest-pipeline-go"
95 | },
96 | "podTemplate": {
97 | "securityContext": {
98 | "fsGroup": 0,
99 | "runAsGroup": 0,
100 | "runAsUser": 0
101 | }
102 | },
103 | "serviceAccountName": "build-robot",
104 | "timeout": "1h0m0s",
105 | "workspaces": [
106 | {
107 | "name": "workspace",
108 | "volumeClaimTemplate": {
109 | "metadata": {
110 | "creationTimestamp": null
111 | },
112 | "spec": {
113 | "accessModes": [
114 | "ReadWriteOnce"
115 | ],
116 | "resources": {
117 | "requests": {
118 | "storage": "1Gi"
119 | }
120 | }
121 | },
122 | "status": {}
123 | }
124 | }
125 | ]
126 | },
127 | "status": {
128 | "completionTime": "2022-10-25T18:42:30Z",
129 | "conditions": [
130 | {
131 | "lastTransitionTime": "2022-10-25T18:42:30Z",
132 | "message": "Tasks Completed: 4 (Failed: 0, Cancelled 0), Skipped: 1",
133 | "reason": "Completed",
134 | "status": "True",
135 | "type": "Succeeded"
136 | }
137 | ],
138 | "finallyStartTime": "2022-10-25T18:42:22Z",
139 | "pipelineSpec": {
140 | "finally": [
141 | {
142 | "name": "github-commit-status-error",
143 | "params": [
144 | {
145 | "name": "statusUrl",
146 | "value": "https://api.github.com/repos/jquad-group/sample-go-application/statuses/ab43364d1c192228832068e93c0a1357412e48af"
147 | },
148 | {
149 | "name": "description",
150 | "value": "The build has failed."
151 | },
152 | {
153 | "name": "state",
154 | "value": "error"
155 | },
156 | {
157 | "name": "targetUrl",
158 | "value": "https://rancher.jquad.rocks/k8s/clusters/local/api/v1/namespaces/tekton-pipelines/services/http:tekton-dashboard:9097/proxy/#/pipelineruns"
159 | },
160 | {
161 | "name": "context",
162 | "value": "jquad-group/tekton-ci"
163 | },
164 | {
165 | "name": "secretName",
166 | "value": "git-clone"
167 | },
168 | {
169 | "name": "usernameKey",
170 | "value": "username"
171 | },
172 | {
173 | "name": "passwordKey",
174 | "value": "password"
175 | }
176 | ],
177 | "taskRef": {
178 | "kind": "Task",
179 | "name": "github-commit-status"
180 | },
181 | "when": [
182 | {
183 | "input": "$(tasks.build-and-test-task-go.status)",
184 | "operator": "in",
185 | "values": [
186 | "Failed"
187 | ]
188 | }
189 | ]
190 | },
191 | {
192 | "name": "github-commit-status-success",
193 | "params": [
194 | {
195 | "name": "statusUrl",
196 | "value": "https://api.github.com/repos/jquad-group/sample-go-application/statuses/ab43364d1c192228832068e93c0a1357412e48af"
197 | },
198 | {
199 | "name": "description",
200 | "value": "The build was successfull."
201 | },
202 | {
203 | "name": "state",
204 | "value": "success"
205 | },
206 | {
207 | "name": "targetUrl",
208 | "value": "https://rancher.jquad.rocks/k8s/clusters/local/api/v1/namespaces/tekton-pipelines/services/http:tekton-dashboard:9097/proxy/#/pipelineruns"
209 | },
210 | {
211 | "name": "context",
212 | "value": "jquad-group/tekton-ci"
213 | },
214 | {
215 | "name": "secretName",
216 | "value": "git-clone"
217 | },
218 | {
219 | "name": "usernameKey",
220 | "value": "username"
221 | },
222 | {
223 | "name": "passwordKey",
224 | "value": "password"
225 | }
226 | ],
227 | "taskRef": {
228 | "kind": "Task",
229 | "name": "github-commit-status"
230 | },
231 | "when": [
232 | {
233 | "input": "$(tasks.build-and-test-task-go.status)",
234 | "operator": "in",
235 | "values": [
236 | "Succeeded"
237 | ]
238 | }
239 | ]
240 | }
241 | ],
242 | "params": [
243 | {
244 | "name": "repo-url",
245 | "type": "string"
246 | },
247 | {
248 | "default": "",
249 | "description": "The git repository URL to clone from.",
250 | "name": "repo-url-alternate",
251 | "type": "string"
252 | },
253 | {
254 | "description": "The git branch to clone.",
255 | "name": "branch-name",
256 | "type": "string"
257 | },
258 | {
259 | "description": "the git project name",
260 | "name": "projectname",
261 | "type": "string"
262 | },
263 | {
264 | "description": "the name of the image",
265 | "name": "repositoryName",
266 | "type": "string"
267 | },
268 | {
269 | "description": "The build context used by Kaniko",
270 | "name": "pathToContext",
271 | "type": "string"
272 | },
273 | {
274 | "default": "",
275 | "description": "subdirectory",
276 | "name": "subdirectory",
277 | "type": "string"
278 | },
279 | {
280 | "name": "statusUrl",
281 | "type": "string"
282 | },
283 | {
284 | "default": "pending",
285 | "name": "statePending",
286 | "type": "string"
287 | },
288 | {
289 | "default": "The build was successfull.",
290 | "name": "descriptionSuccess",
291 | "type": "string"
292 | },
293 | {
294 | "default": "success",
295 | "name": "stateSuccess",
296 | "type": "string"
297 | },
298 | {
299 | "default": "error",
300 | "name": "stateError",
301 | "type": "string"
302 | },
303 | {
304 | "default": "The build has failed.",
305 | "name": "descriptionError",
306 | "type": "string"
307 | },
308 | {
309 | "default": "The build is currently running.",
310 | "name": "descriptionPending",
311 | "type": "string"
312 | },
313 | {
314 | "default": "https://rancher.jquad.rocks/pipeline",
315 | "name": "targetUrl",
316 | "type": "string"
317 | },
318 | {
319 | "default": "jquad-group/tekton-ci",
320 | "name": "context",
321 | "type": "string"
322 | },
323 | {
324 | "name": "githubSecretName",
325 | "type": "string"
326 | },
327 | {
328 | "name": "githubUsernameKey",
329 | "type": "string"
330 | },
331 | {
332 | "name": "githubPasswordKey",
333 | "type": "string"
334 | }
335 | ],
336 | "tasks": [
337 | {
338 | "name": "clone",
339 | "params": [
340 | {
341 | "name": "url",
342 | "value": "git@github.com:jquad-group/sample-go-application.git"
343 | },
344 | {
345 | "name": "url-alternate",
346 | "value": "git@github.com:jquad-group/sample-go-application.git"
347 | },
348 | {
349 | "name": "revision",
350 | "value": "feature/added-catalog-info"
351 | },
352 | {
353 | "name": "refspec",
354 | "value": "+refs/tags/*:refs/tags/* +refs/heads/*:refs/heads/*"
355 | },
356 | {
357 | "name": "deleteExisting",
358 | "value": "true"
359 | },
360 | {
361 | "name": "sslVerify",
362 | "value": "false"
363 | },
364 | {
365 | "name": "depth",
366 | "value": "1"
367 | },
368 | {
369 | "name": "subdirectory",
370 | "value": ""
371 | }
372 | ],
373 | "taskRef": {
374 | "kind": "Task",
375 | "name": "git-clone"
376 | },
377 | "workspaces": [
378 | {
379 | "name": "repo",
380 | "workspace": "workspace"
381 | }
382 | ]
383 | },
384 | {
385 | "name": "github-commit-status-pending",
386 | "params": [
387 | {
388 | "name": "statusUrl",
389 | "value": "https://api.github.com/repos/jquad-group/sample-go-application/statuses/ab43364d1c192228832068e93c0a1357412e48af"
390 | },
391 | {
392 | "name": "description",
393 | "value": "The build is currently running."
394 | },
395 | {
396 | "name": "state",
397 | "value": "pending"
398 | },
399 | {
400 | "name": "targetUrl",
401 | "value": "https://rancher.jquad.rocks/k8s/clusters/local/api/v1/namespaces/tekton-pipelines/services/http:tekton-dashboard:9097/proxy/#/pipelineruns"
402 | },
403 | {
404 | "name": "context",
405 | "value": "jquad-group/tekton-ci"
406 | },
407 | {
408 | "name": "secretName",
409 | "value": "git-clone"
410 | },
411 | {
412 | "name": "usernameKey",
413 | "value": "username"
414 | },
415 | {
416 | "name": "passwordKey",
417 | "value": "password"
418 | }
419 | ],
420 | "runAfter": [
421 | "clone"
422 | ],
423 | "taskRef": {
424 | "kind": "Task",
425 | "name": "github-commit-status"
426 | }
427 | },
428 | {
429 | "name": "build-and-test-task-go",
430 | "runAfter": [
431 | "github-commit-status-pending"
432 | ],
433 | "taskRef": {
434 | "kind": "Task",
435 | "name": "build-and-test-task-go"
436 | },
437 | "workspaces": [
438 | {
439 | "name": "repo",
440 | "workspace": "workspace"
441 | }
442 | ]
443 | }
444 | ],
445 | "workspaces": [
446 | {
447 | "name": "workspace"
448 | }
449 | ]
450 | },
451 | "skippedTasks": [
452 | {
453 | "name": "github-commit-status-error",
454 | "reason": "When Expressions evaluated to false",
455 | "whenExpressions": [
456 | {
457 | "input": "Succeeded",
458 | "operator": "in",
459 | "values": [
460 | "Failed"
461 | ]
462 | }
463 | ]
464 | }
465 | ],
466 | "startTime": "2022-10-25T18:41:36Z",
467 | "taskRuns": {
468 | "feature-added-catalog-info-xdjk9-build-and-test-task-go": {
469 | "pipelineTaskName": "build-and-test-task-go",
470 | "status": {
471 | "completionTime": "2022-10-25T18:42:22Z",
472 | "conditions": [
473 | {
474 | "lastTransitionTime": "2022-10-25T18:42:22Z",
475 | "message": "All Steps have completed executing",
476 | "reason": "Succeeded",
477 | "status": "True",
478 | "type": "Succeeded"
479 | }
480 | ],
481 | "podName": "feature-added-catalog-info-xdjk9-build-and-test-task-go-pod",
482 | "startTime": "2022-10-25T18:42:07Z",
483 | "steps": [
484 | {
485 | "container": "step-test",
486 | "imageID": "docker.io/library/golang@sha256:b1ff3263f543b4c458e172f3df429a3e7dfed29d4359ab839a614f903252e1df",
487 | "name": "test",
488 | "terminated": {
489 | "containerID": "containerd://fb2c58d4ac624483829c480e7a1b1532296741645a11fe7b63c93bc2d36c54d0",
490 | "exitCode": 0,
491 | "finishedAt": "2022-10-25T18:42:21Z",
492 | "reason": "Completed",
493 | "startedAt": "2022-10-25T18:42:21Z"
494 | }
495 | },
496 | {
497 | "container": "step-build",
498 | "imageID": "docker.io/library/golang@sha256:b1ff3263f543b4c458e172f3df429a3e7dfed29d4359ab839a614f903252e1df",
499 | "name": "build",
500 | "terminated": {
501 | "containerID": "containerd://d83b2cdce00d458ac38c428c321aafa9e8f0014a561ef41b7731e28520f7ee00",
502 | "exitCode": 0,
503 | "finishedAt": "2022-10-25T18:42:21Z",
504 | "reason": "Completed",
505 | "startedAt": "2022-10-25T18:42:21Z"
506 | }
507 | }
508 | ],
509 | "taskSpec": {
510 | "steps": [
511 | {
512 | "image": "golang:1.17.11",
513 | "imagePullPolicy": "IfNotPresent",
514 | "name": "test",
515 | "resources": {},
516 | "script": "echo \"cd /workspace/repo\"\necho \"make test\""
517 | },
518 | {
519 | "image": "golang:1.17.11",
520 | "imagePullPolicy": "IfNotPresent",
521 | "name": "build",
522 | "resources": {},
523 | "script": "echo \"cd /workspace/repo\"\necho \"make\" "
524 | }
525 | ],
526 | "workspaces": [
527 | {
528 | "name": "repo"
529 | }
530 | ]
531 | }
532 | }
533 | },
534 | "feature-added-catalog-info-xdjk9-clone": {
535 | "pipelineTaskName": "clone",
536 | "status": {
537 | "completionTime": "2022-10-25T18:41:58Z",
538 | "conditions": [
539 | {
540 | "lastTransitionTime": "2022-10-25T18:41:58Z",
541 | "message": "All Steps have completed executing",
542 | "reason": "Succeeded",
543 | "status": "True",
544 | "type": "Succeeded"
545 | }
546 | ],
547 | "podName": "feature-added-catalog-info-xdjk9-clone-pod",
548 | "resourcesResult": [
549 | {
550 | "key": "url",
551 | "value": "git@github.com:jquad-group/sample-go-application.git"
552 | }
553 | ],
554 | "startTime": "2022-10-25T18:41:36Z",
555 | "steps": [
556 | {
557 | "container": "step-clone",
558 | "imageID": "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:ec618dfa0fc23ae90105135be96bb8c0f9250bfd7682d9d5bab845ab6fd50528",
559 | "name": "clone",
560 | "terminated": {
561 | "containerID": "containerd://8bad7073f6311c42171babfd74fe073ea3275c16308fbfffee2b2c81f25fc7d9",
562 | "exitCode": 0,
563 | "finishedAt": "2022-10-25T18:41:57Z",
564 | "message": "[{\"key\":\"commit\",\"value\":\"ab43364d1c192228832068e93c0a1357412e48af\",\"type\":1},{\"key\":\"lock-pid\",\"value\":\"caa37cd2-f38b-4689-add3-8efeac75715f\\n\",\"type\":1},{\"key\":\"url\",\"value\":\"git@github.com:jquad-group/sample-go-application.git\"}]",
565 | "reason": "Completed",
566 | "startedAt": "2022-10-25T18:41:56Z"
567 | }
568 | }
569 | ],
570 | "taskResults": [
571 | {
572 | "name": "commit",
573 | "type": "string",
574 | "value": "ab43364d1c192228832068e93c0a1357412e48af"
575 | },
576 | {
577 | "name": "lock-pid",
578 | "type": "string",
579 | "value": "caa37cd2-f38b-4689-add3-8efeac75715f\n"
580 | }
581 | ],
582 | "taskSpec": {
583 | "params": [
584 | {
585 | "description": "git https or ssh url to clone",
586 | "name": "url",
587 | "type": "string"
588 | },
589 | {
590 | "default": "",
591 | "description": "git https or ssh url to clone if not provided, url is used as default",
592 | "name": "url-alternate",
593 | "type": "string"
594 | },
595 | {
596 | "default": "master",
597 | "description": "git revision to checkout (branch, tag, sha, ref)",
598 | "name": "revision",
599 | "type": "string"
600 | },
601 | {
602 | "default": "",
603 | "description": "(optional) git refspec to fetch before checking out revision",
604 | "name": "refspec",
605 | "type": "string"
606 | },
607 | {
608 | "default": "false",
609 | "description": "defines if the resource should initialize and fetch the submodules",
610 | "name": "submodules",
611 | "type": "string"
612 | },
613 | {
614 | "default": "1",
615 | "description": "performs a shallow clone where only the most recent commit(s) will be fetched",
616 | "name": "depth",
617 | "type": "string"
618 | },
619 | {
620 | "default": "true",
621 | "description": "defines if http.sslVerify should be set to true or false in the global git config",
622 | "name": "sslVerify",
623 | "type": "string"
624 | },
625 | {
626 | "default": "",
627 | "description": "subdirectory inside the \"output\" workspace to clone the git repo into",
628 | "name": "subdirectory",
629 | "type": "string"
630 | },
631 | {
632 | "default": "false",
633 | "description": "clean out the contents of the repo's destination directory (if it already exists) before trying to clone the repo there",
634 | "name": "deleteExisting",
635 | "type": "string"
636 | },
637 | {
638 | "default": "",
639 | "description": "git HTTP proxy server for non-SSL requests",
640 | "name": "httpProxy",
641 | "type": "string"
642 | },
643 | {
644 | "default": "",
645 | "description": "git HTTPS proxy server for SSL requests",
646 | "name": "httpsProxy",
647 | "type": "string"
648 | },
649 | {
650 | "default": "",
651 | "description": "git no proxy - opt out of proxying HTTP/HTTPS requests",
652 | "name": "noProxy",
653 | "type": "string"
654 | },
655 | {
656 | "default": "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.37.0",
657 | "description": "The image used where the git-init binary is.",
658 | "name": "gitInitImage",
659 | "type": "string"
660 | }
661 | ],
662 | "results": [
663 | {
664 | "description": "The precise commit SHA that was fetched by this Task",
665 | "name": "commit",
666 | "type": "string"
667 | },
668 | {
669 | "description": "The final execution status should be \"Succeeded\", can be replaced in \u003e tekton v20.x",
670 | "name": "status",
671 | "type": "string"
672 | },
673 | {
674 | "description": "the pid set by the first step",
675 | "name": "lock-pid",
676 | "type": "string"
677 | }
678 | ],
679 | "steps": [
680 | {
681 | "image": "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.37.0",
682 | "imagePullPolicy": "Always",
683 | "name": "clone",
684 | "resources": {},
685 | "script": "# check if given alternate url starts with https\nURL=\"git@github.com:jquad-group/sample-go-application.git\"\nif echo \"git@github.com:jquad-group/sample-go-application.git\" | grep -E \"^[[:blank:]]*http[s]{0,1}://.+$\" ; then\n URL=\"git@github.com:jquad-group/sample-go-application.git\"\nfi\n\nif echo \"$URL\" | grep -v -E \"^[[:blank:]]*http[s]{0,1}://.+$\" ; then\n echo \"Provide a valid https URI, either in url or in url-alternate\"\nfi\n\nCHECKOUT_DIR=\"/workspace/repo/\"\n\n# Locks\nTARGETDIR=\"/workspace/repo\"\nLOCKFOLDER=$TARGETDIR/.lock\nPIDFILE=$LOCKFOLDER/.pid\nLOGFILE=log\ndeleteLock(){\n pid=$(cat /tekton/results/lock-pid)\n content=$(cat $PIDFILE)\n if [ \"$pid\" == \"$content\" ];then\n echo \"deleting lock\"\n rm -rf $LOCKFOLDER;\n fi\n}\n\n# For exit not applicable in this use case\n#trapfunctions\ntrap_exit() {\n echo \"trapping exit signal\" \u003e\u00262\n deleteLock\n}\ntrap_int() {\n echo \"trapping int signal\" \u003e\u00262\n deleteLock\n exit 1\n}\ntrap_term() {\n echo \"trapping term signal\" \u003e\u00262\n deleteLock\n exit 3\n}\n#\n#\ntrap trap_exit EXIT\ntrap trap_int INT\ntrap trap_term TERM\n\naquireLock(){\n mkdir -pv $TARGETDIR\n let i=1\n while ! mkdir $LOCKFOLDER ; do\n echo \"waiting\"\n sleep $i\n let i+=2;\n if $i \u003e= 60 ; then\n # don't wait indefinitely\n exit 4\n fi\n done\n\n PID=caa37cd2-f38b-4689-add3-8efeac75715f\n echo \"aquired Lock\"\n echo $PID \u003e $PIDFILE\n echo $PID \u003e /tekton/results/lock-pid\n echo $PID\n touch $LOCKFOLDER\n}\n\n\ncleandir() {\n # Delete any existing contents of the repo directory if it exists.\n #\n # We don't just \"rm -rf $CHECKOUT_DIR\" because $CHECKOUT_DIR might be \"/\"\n # or the root of a mounted volume.\n if [[ -d \"$CHECKOUT_DIR\" ]] ; then\n # Delete non-hidden files and directories\n rm -rf \"$CHECKOUT_DIR\"/*\n # Delete files and directories starting with . but excluding .. and .lock\n rm -rf $CHECKOUT_DIR/.[!.]*[!lock]*\n # Delete files and directories starting with .. plus any other character\n rm -rf \"$CHECKOUT_DIR\"/..?*\n fi\n}\n\naquireLock\n\nif [[ \"true\" == \"true\" ]] ; then\n cleandir\nfi\ntest -z \"\" || export HTTP_PROXY=\ntest -z \"\" || export HTTPS_PROXY=\ntest -z \"\" || export NO_PROXY=\n/ko-app/git-init \\\n -url \"$URL\" \\\n -revision \"feature/added-catalog-info\" \\\n -refspec \"+refs/tags/*:refs/tags/* +refs/heads/*:refs/heads/*\" \\\n -path \"$CHECKOUT_DIR\" \\\n -sslVerify=\"false\" \\\n -submodules=\"false\" \\\n -depth \"1\"\ncd \"$CHECKOUT_DIR\"\nRESULT_SHA=\"$(git rev-parse HEAD | tr -d '\\n')\"\n\nEXIT_CODE=\"$?\"\n# deleted by trap\n# deleteLock\nif [ \"$EXIT_CODE\" != 0 ]\nthen\n exit $EXIT_CODE\nfi\n# Make sure we don't add a trailing newline to the result!\necho -n \"$RESULT_SHA\" \u003e /tekton/results/commit"
686 | }
687 | ],
688 | "workspaces": [
689 | {
690 | "description": "The git repo will be cloned onto the volume backing this workspace",
691 | "name": "repo"
692 | }
693 | ]
694 | }
695 | }
696 | },
697 | "feature-added-catalog-info-xdjk9-github-commit-status-pending": {
698 | "pipelineTaskName": "github-commit-status-pending",
699 | "status": {
700 | "completionTime": "2022-10-25T18:42:06Z",
701 | "conditions": [
702 | {
703 | "lastTransitionTime": "2022-10-25T18:42:06Z",
704 | "message": "All Steps have completed executing",
705 | "reason": "Succeeded",
706 | "status": "True",
707 | "type": "Succeeded"
708 | }
709 | ],
710 | "podName": "feature-added-catalog-info-eff154312f63d856ff7e731ca728fd3c-pod",
711 | "startTime": "2022-10-25T18:41:58Z",
712 | "steps": [
713 | {
714 | "container": "step-set-github-commit-status",
715 | "imageID": "docker.io/alpine/curl@sha256:81372de8c566f2d731bde924bed45230018e6d7c21d051c15e283eb8e06dfa2d",
716 | "name": "set-github-commit-status",
717 | "terminated": {
718 | "containerID": "containerd://7a0e6b0c6a032743c2fda5bfb7a0b9e44be36eb07394fb46a66eae78db2f240b",
719 | "exitCode": 0,
720 | "finishedAt": "2022-10-25T18:42:06Z",
721 | "reason": "Completed",
722 | "startedAt": "2022-10-25T18:42:06Z"
723 | }
724 | }
725 | ],
726 | "taskSpec": {
727 | "params": [
728 | {
729 | "name": "statusUrl",
730 | "type": "string"
731 | },
732 | {
733 | "default": "success",
734 | "name": "state",
735 | "type": "string"
736 | },
737 | {
738 | "name": "description",
739 | "type": "string"
740 | },
741 | {
742 | "default": "https://rancher.jquad.rocks/pipeline",
743 | "name": "targetUrl",
744 | "type": "string"
745 | },
746 | {
747 | "default": "jquad-group/tekton-ci",
748 | "name": "context",
749 | "type": "string"
750 | },
751 | {
752 | "name": "secretName",
753 | "type": "string"
754 | },
755 | {
756 | "default": "username",
757 | "name": "usernameKey",
758 | "type": "string"
759 | },
760 | {
761 | "default": "password",
762 | "name": "passwordKey",
763 | "type": "string"
764 | }
765 | ],
766 | "steps": [
767 | {
768 | "env": [
769 | {
770 | "name": "GITHUB_PASSWORD",
771 | "valueFrom": {
772 | "secretKeyRef": {
773 | "key": "password",
774 | "name": "git-clone"
775 | }
776 | }
777 | },
778 | {
779 | "name": "GITHUB_USER",
780 | "valueFrom": {
781 | "secretKeyRef": {
782 | "key": "username",
783 | "name": "git-clone"
784 | }
785 | }
786 | }
787 | ],
788 | "image": "alpine/curl:latest",
789 | "imagePullPolicy": "Always",
790 | "name": "set-github-commit-status",
791 | "resources": {},
792 | "script": "curl \\\n-i \\\n-u \\\n\"${GITHUB_USER}:${GITHUB_PASSWORD}\" \\\n-X \\\nPOST \\\n-H \\\n\"Accept: application/vnd.github.v3+json\" \\\nhttps://api.github.com/repos/jquad-group/sample-go-application/statuses/ab43364d1c192228832068e93c0a1357412e48af \\\n-d \\\n\"{\\\"state\\\":\\\"pending\\\",\\\"target_url\\\":\\\"https://rancher.jquad.rocks/k8s/clusters/local/api/v1/namespaces/tekton-pipelines/services/http:tekton-dashboard:9097/proxy/#/pipelineruns\\\",\\\"description\\\":\\\"The build is currently running.\\\",\\\"context\\\":\\\"jquad-group/tekton-ci\\\"}\""
793 | }
794 | ]
795 | }
796 | }
797 | },
798 | "feature-added-catalog-info-xdjk9-github-commit-status-success": {
799 | "pipelineTaskName": "github-commit-status-success",
800 | "status": {
801 | "completionTime": "2022-10-25T18:42:30Z",
802 | "conditions": [
803 | {
804 | "lastTransitionTime": "2022-10-25T18:42:30Z",
805 | "message": "All Steps have completed executing",
806 | "reason": "Succeeded",
807 | "status": "True",
808 | "type": "Succeeded"
809 | }
810 | ],
811 | "podName": "feature-added-catalog-info-6f82ba3e4ddf3855f05e660aa435643a-pod",
812 | "startTime": "2022-10-25T18:42:22Z",
813 | "steps": [
814 | {
815 | "container": "step-set-github-commit-status",
816 | "imageID": "docker.io/alpine/curl@sha256:81372de8c566f2d731bde924bed45230018e6d7c21d051c15e283eb8e06dfa2d",
817 | "name": "set-github-commit-status",
818 | "terminated": {
819 | "containerID": "containerd://d4f158d13389fef26d714708f91f226a58868751a7edc496b3a0f3d16f96e69c",
820 | "exitCode": 0,
821 | "finishedAt": "2022-10-25T18:42:29Z",
822 | "reason": "Completed",
823 | "startedAt": "2022-10-25T18:42:29Z"
824 | }
825 | }
826 | ],
827 | "taskSpec": {
828 | "params": [
829 | {
830 | "name": "statusUrl",
831 | "type": "string"
832 | },
833 | {
834 | "default": "success",
835 | "name": "state",
836 | "type": "string"
837 | },
838 | {
839 | "name": "description",
840 | "type": "string"
841 | },
842 | {
843 | "default": "https://rancher.jquad.rocks/pipeline",
844 | "name": "targetUrl",
845 | "type": "string"
846 | },
847 | {
848 | "default": "jquad-group/tekton-ci",
849 | "name": "context",
850 | "type": "string"
851 | },
852 | {
853 | "name": "secretName",
854 | "type": "string"
855 | },
856 | {
857 | "default": "username",
858 | "name": "usernameKey",
859 | "type": "string"
860 | },
861 | {
862 | "default": "password",
863 | "name": "passwordKey",
864 | "type": "string"
865 | }
866 | ],
867 | "steps": [
868 | {
869 | "env": [
870 | {
871 | "name": "GITHUB_PASSWORD",
872 | "valueFrom": {
873 | "secretKeyRef": {
874 | "key": "password",
875 | "name": "git-clone"
876 | }
877 | }
878 | },
879 | {
880 | "name": "GITHUB_USER",
881 | "valueFrom": {
882 | "secretKeyRef": {
883 | "key": "username",
884 | "name": "git-clone"
885 | }
886 | }
887 | }
888 | ],
889 | "image": "alpine/curl:latest",
890 | "imagePullPolicy": "Always",
891 | "name": "set-github-commit-status",
892 | "resources": {},
893 | "script": "curl \\\n-i \\\n-u \\\n\"${GITHUB_USER}:${GITHUB_PASSWORD}\" \\\n-X \\\nPOST \\\n-H \\\n\"Accept: application/vnd.github.v3+json\" \\\nhttps://api.github.com/repos/jquad-group/sample-go-application/statuses/ab43364d1c192228832068e93c0a1357412e48af \\\n-d \\\n\"{\\\"state\\\":\\\"success\\\",\\\"target_url\\\":\\\"https://rancher.jquad.rocks/k8s/clusters/local/api/v1/namespaces/tekton-pipelines/services/http:tekton-dashboard:9097/proxy/#/pipelineruns\\\",\\\"description\\\":\\\"The build was successfull.\\\",\\\"context\\\":\\\"jquad-group/tekton-ci\\\"}\""
894 | }
895 | ]
896 | }
897 | }
898 | }
899 | }
900 | }
901 | }
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/CollapsibleTable/index.ts:
--------------------------------------------------------------------------------
1 | export { CollapsibleTable } from './CollapsibleTable';
2 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/CollapsibleTableRow/CollapsibleTableRow.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StatusError, StatusOK, StatusPending, StatusRunning, StatusWarning } from '@backstage/core-components';
3 | // eslint-disable-next-line no-restricted-imports
4 | import { KeyboardArrowDown, KeyboardArrowUp } from '@material-ui/icons';
5 | import { Table, TableBody, TableRow, TableCell, IconButton, Collapse, TableHead } from '@material-ui/core';
6 | /* ignore lint error for internal dependencies */
7 | /* eslint-disable */
8 | import { Condition, PipelineRun } from '../../types';
9 | import { TaskRunRow } from '../TaskRunRow';
10 | /* eslint-enable */
11 |
12 |
13 | function StatusComponent(props: { conditions: [Condition]; }): JSX.Element {
14 | if (props.conditions !== undefined) {
15 | if (props.conditions[0].reason === 'Created') {
16 | return ;
17 | } else
18 | if (props.conditions[0].reason === 'Running') {
19 | return ;
20 | } else
21 | if (props.conditions[0].reason === 'Completed') {
22 | return ;
23 | } else
24 | if (props.conditions[0].reason === 'Succeeded') {
25 | return ;
26 | } else
27 | if (props.conditions[0].reason === 'PipelineRunCancelled') {
28 | return ;
29 | } else
30 | if (props.conditions[0].reason === 'Failed') {
31 | return ;
32 | } else
33 | if (props.conditions[0].reason === 'Error') {
34 | return ;
35 | }
36 | } else {
37 | return ;
38 | }
39 | return ;
40 |
41 | }
42 |
43 | export function CollapsibleTableRow(props: { clusterName: string, pipelineRun: PipelineRun }) {
44 | const { clusterName, pipelineRun } = props;
45 | const [open, setOpen] = React.useState(false);
46 |
47 | if (!pipelineRun || !pipelineRun.status) {
48 | return null; // or handle this case accordingly
49 | }
50 |
51 | if (pipelineRun.status.completionTime === undefined) {
52 | pipelineRun.status.completionTime = "";
53 | }
54 |
55 | return (
56 |
57 |
58 |
59 | setOpen(!open)}
63 | >
64 | {open ? : }
65 |
66 |
67 |
68 | {pipelineRun.metadata.name}
69 |
70 | {pipelineRun.metadata.namespace}
71 | { pipelineRun.status.conditions !== undefined && (
72 |
73 | {pipelineRun.status.conditions[0].reason}
74 | )}
75 | { pipelineRun.status.conditions === undefined && (
76 | Pending
77 | )}
78 | {pipelineRun.status.startTime}
79 | {pipelineRun.status.completionTime}
80 | Link
81 |
82 |
83 |
84 |
85 |
86 |
87 | Name
88 | Step
89 | Status
90 | Start Time
91 | Completition Time
92 | Log
93 |
94 |
95 |
96 | {pipelineRun.taskRuns !== undefined &&
97 | pipelineRun.taskRuns.map((taskRunRow) => (
98 |
99 | ))}
100 |
101 |
102 |
103 |
104 |
105 | );
106 | }
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/CollapsibleTableRow/index.ts:
--------------------------------------------------------------------------------
1 | export { CollapsibleTableRow } from './CollapsibleTableRow';
2 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/StepLog/StepLog.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | Button,
4 | Dialog,
5 | DialogActions,
6 | DialogContent,
7 | DialogTitle,
8 | IconButton,
9 | Toolbar,
10 | Typography,
11 | } from "@material-ui/core";
12 | import { makeStyles } from "@material-ui/core/styles";
13 | import Close from "@material-ui/icons/Close";
14 | import Fullscreen from "@material-ui/icons/Fullscreen";
15 |
16 | interface Props {
17 | opened: boolean;
18 | text: string;
19 | }
20 |
21 | const useStyles = makeStyles((theme) => ({
22 | dialogContent: {
23 | maxHeight: "90vh",
24 | overflowY: "auto",
25 | whiteSpace: "pre-wrap",
26 | fontFamily: "monospace",
27 | },
28 | toolbar: {
29 | display: "flex",
30 | justifyContent: "space-between",
31 | },
32 | success: {
33 | color: 'green',
34 | marginLeft: theme.spacing(1),
35 | },
36 | }));
37 |
38 | export const StepLog: React.FC = ({ opened, text }) => {
39 | const classes = useStyles();
40 | const [open, setOpen] = useState(opened);
41 | const [copied, setCopied] = useState(false);
42 | const [isFullscreen, setIsFullscreen] = useState(false);
43 |
44 | const toggleFullscreen = () => {
45 | setIsFullscreen(prevState => !prevState);
46 | };
47 |
48 | const handleCopy = () => {
49 | navigator.clipboard.writeText(text);
50 | setCopied(true);
51 | setTimeout(() => setCopied(false), 2000);
52 | };
53 |
54 | return (
55 |
87 | );
88 | };
89 |
90 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/StepLog/index.ts:
--------------------------------------------------------------------------------
1 | export { StepLog } from './StepLog';
2 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/StepRow/StepRow.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { StatusError, StatusOK, StatusPending, StatusRunning, StatusWarning } from '@backstage/core-components';
3 | // eslint-disable-next-line no-restricted-imports
4 | import { TableRow, TableCell, Button, CircularProgress } from '@material-ui/core';
5 | import { useApi } from '@backstage/core-plugin-api';
6 | import { kubernetesApiRef } from '@backstage/plugin-kubernetes';
7 |
8 | /* ignore lint error for internal dependencies */
9 | /* eslint-disable */
10 | import { Step } from '../../types';
11 | import { StepLog } from '../StepLog';
12 |
13 |
14 | function StatusComponent(props: { reason: string; }): JSX.Element {
15 | if (props.reason === 'Created') {
16 | return ;
17 | } else
18 | if (props.reason === 'Running') {
19 | return ;
20 | } else
21 | if (props.reason === 'Completed') {
22 | return ;
23 | } else
24 | if (props.reason === 'Succeeded') {
25 | return ;
26 | } else
27 | if (props.reason === 'PipelineRunCancelled') {
28 | return ;
29 | } else
30 | if (props.reason === 'Failed') {
31 | return ;
32 | }
33 | if (props.reason === 'Error') {
34 | return ;
35 | }
36 | return ;
37 |
38 | }
39 |
40 | export function StepRow(props: { clusterName: string, namespace: string, podName: string, step: Step }) {
41 | const { clusterName, namespace, podName, step } = props;
42 |
43 | const [data, setData] = React.useState({data: ""});
44 | const [isLoading, setIsLoading] = React.useState(false);
45 |
46 | const k8s = useApi(kubernetesApiRef);
47 |
48 | const handleClick = async (step: Step) => {
49 | setIsLoading(true);
50 | const url = `/api/v1/namespaces/${namespace}/pods/${podName}/log?container=step-${step.name}`;
51 |
52 | k8s.proxy({
53 | clusterName: clusterName,
54 | path: url,
55 | }).then(async (res) => {
56 | setData({...data, data: await res.text()});
57 | step.log = data.data;
58 | setIsLoading(false);
59 | });
60 |
61 | };
62 |
63 | return (
64 |
65 |
66 |
67 | {step.name}
68 |
69 | {step.terminated !== undefined && (
70 | <>
71 | {step.terminated.reason}
72 |
73 | {step.terminated.startedAt}
74 |
75 | {step.terminated.finishedAt}
76 |
77 |
78 | {isLoading && (
79 |
80 | )}
81 |
82 | {!isLoading && data.data !== "" && (
83 |
84 | )}
85 | >
86 | )}
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/StepRow/index.ts:
--------------------------------------------------------------------------------
1 | export { StepRow } from './StepRow';
2 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/TaskRunRow/TaskRunRow.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | // eslint-disable-next-line no-restricted-imports
3 | import { TableRow, TableCell } from '@material-ui/core';
4 | /* ignore lint error for internal dependencies */
5 | /* eslint-disable */
6 | import { TaskRun } from '../../types';
7 | import { StepRow } from '../StepRow';
8 |
9 | export function TaskRunRow(props: { clusterName: string, taskRun: TaskRun }) {
10 | const { clusterName, taskRun } = props;
11 |
12 | return (
13 |
14 |
15 | {taskRun.status !== undefined && taskRun.status.steps !== undefined && (
16 |
17 | {taskRun.metadata.name}
18 |
19 | )}
20 |
21 | {taskRun.status !== undefined && taskRun.status.steps !== undefined &&
22 | taskRun.status.steps.map((step) => (
23 |
24 | ))}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/TaskRunRow/index.ts:
--------------------------------------------------------------------------------
1 | export { TaskRunRow } from './TaskRunRow';
2 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/TektonDashboard/TektoDashboardComponent.tsx:
--------------------------------------------------------------------------------
1 | import { Entity } from '@backstage/catalog-model';
2 | import {
3 | Content,
4 | ContentHeader,
5 | Page,
6 | Progress,
7 | ResponseErrorPanel,
8 | } from '@backstage/core-components';
9 |
10 | /* ignore lint error for internal dependencies */
11 | /* eslint-disable */
12 | import { Box, FormControl, Grid, InputLabel, MenuItem, Select } from '@material-ui/core';
13 | import { KubernetesApi, kubernetesApiRef, useKubernetesObjects } from '@backstage/plugin-kubernetes';
14 | import { PipelineRun, Cluster, TaskRun } from '../../types';
15 | import { CollapsibleTable } from '../CollapsibleTable';
16 | import React, { useEffect, useState } from 'react';
17 | import { useApi } from '@backstage/core-plugin-api';
18 |
19 | type KubernetesContentProps = {
20 | entity: Entity;
21 | refreshIntervalMs?: number;
22 | children?: React.ReactNode;
23 | };
24 |
25 | /*
26 | const getTaskRunsForPipelineRun = async (clusterName: string, pipelineRun: PipelineRun, k8s: KubernetesApi): Promise> => {
27 | const namespace = pipelineRun.metadata.namespace;
28 | const pipelineRunName = pipelineRun.metadata.name;
29 | const url = `/apis/tekton.dev/v1/namespaces/${namespace}/taskruns?labelSelector=tekton.dev/pipelineRun=${pipelineRunName}`
30 |
31 | try {
32 | const response = await k8s.proxy({
33 | clusterName: clusterName,
34 | path: url,
35 | });
36 |
37 |
38 | if (response && response.status === 200) {
39 | const responseData = await response.json();
40 | if (responseData && responseData.items) {
41 | const taskRuns: Array = responseData.items;
42 | return taskRuns;
43 | }
44 | }
45 |
46 | return []
47 |
48 | } catch (error) {
49 | console.error(`Error fetching TaskRuns for PipelineRun ${pipelineRunName}:`, error);
50 | return []
51 | // throw error; // Rethrow the error to be caught by the calling function
52 | }
53 | };
54 | */
55 |
56 | const fetchClusterNames = async (k8s: KubernetesApi): Promise> => {
57 | const clusters: Array = [];
58 | for (const cluster of await k8s.getClusters()) {
59 | let tmpCluster = {} as Cluster;
60 | tmpCluster.name = cluster.name;
61 | clusters.push(tmpCluster);
62 | }
63 |
64 | return clusters;
65 | };
66 |
67 | const getTaskRunsForCluster = async (clusterName: string, tektonApi: string, labelSelector: string, k8s: KubernetesApi): Promise> => {
68 |
69 | const url = `/apis/tekton.dev/${tektonApi}/taskruns?labelSelector=${labelSelector}&limit=500`
70 |
71 | try {
72 | const response = await k8s.proxy({
73 | clusterName: clusterName,
74 | path: url,
75 | });
76 |
77 | if (response && response.status === 200) {
78 | const responseData = await response.json();
79 | if (responseData && responseData.items) {
80 | const taskRuns: Array = responseData.items;
81 | return taskRuns;
82 | }
83 | }
84 |
85 | return []
86 |
87 | } catch (error) {
88 | console.error(`Error fetching TaskRuns:`, error);
89 | return []
90 | }
91 | };
92 |
93 | const getPipelineRunsForCluster = async (clusterName: string, tektonApi: string, labelSelector: string, k8s: KubernetesApi): Promise> => {
94 |
95 | const url = `/apis/tekton.dev/${tektonApi}/pipelineruns?labelSelector=${labelSelector}&limit=500`
96 |
97 | try {
98 | const response = await k8s.proxy({
99 | clusterName: clusterName,
100 | path: url,
101 | });
102 |
103 | if (response && response.status === 200) {
104 | const responseData = await response.json();
105 | if (responseData && responseData.items) {
106 | const pipelineRuns: Array = responseData.items;
107 | return pipelineRuns;
108 | }
109 | }
110 |
111 | return []
112 |
113 | } catch (error) {
114 | console.error(`Error fetching PipelineRuns:`, error);
115 | return []
116 | }
117 | };
118 |
119 |
120 | export const TektonDashboardComponent = ({
121 | entity,
122 | refreshIntervalMs,
123 | }: KubernetesContentProps) => {
124 | const { kubernetesObjects, error } = useKubernetesObjects(
125 | entity,
126 | refreshIntervalMs,
127 | );
128 |
129 | const [loading, setLoading] = useState(true); // State to manage loading state
130 | const [selectedCluster, setSelectedCluster] = useState(null);
131 | const [clusters, setClusters] = useState>([]);
132 | const [fetchingData, setFetchingData] = useState(false); // State to manage fetching data state
133 | const k8s = useApi(kubernetesApiRef);
134 |
135 | useEffect(() => {
136 | const fetchClusters = async () => {
137 | try {
138 | const fetchedClusters = await fetchClusterNames(k8s);
139 | setClusters(fetchedClusters);
140 | setLoading(false);
141 | } catch (error) {
142 | console.error('Error fetching cluster names:', error);
143 | }
144 | };
145 |
146 | fetchClusters();
147 | }, []);
148 |
149 | useEffect(() => {
150 | // This useEffect will run whenever kubernetesObjects changes
151 | const loadedCluster = kubernetesObjects?.items.find(obj => obj.cluster.name === selectedCluster);
152 | if (loadedCluster === undefined && selectedCluster) {
153 | setFetchingData(true);
154 | }
155 | if (loadedCluster !== undefined && selectedCluster !== null) {
156 | const fetchTaskRuns = async () => {
157 | // Create a copy of the clusters array
158 | const updatedClusters = [...clusters];
159 |
160 | // Find the selected cluster in the array
161 | const selectedClusterIndex = updatedClusters.findIndex(cluster => cluster.name === selectedCluster);
162 |
163 | if (loadedCluster.errors.length === 0) {
164 | // create tmpCluster
165 | const tmpCluster = {} as Cluster;
166 | tmpCluster.name = loadedCluster.cluster.name;
167 |
168 | // get all PipelineRuns
169 | let pipelineRunsPromise: Promise;
170 | if (entity?.metadata?.annotations?.["tektonci/api"]) {
171 | const tektonApi = entity?.metadata?.annotations?.["tektonci/api"];
172 | if (entity?.metadata?.annotations?.["backstage.io/kubernetes-label-selector"]) {
173 | pipelineRunsPromise = getPipelineRunsForCluster(tmpCluster.name, tektonApi, entity.metadata.annotations["backstage.io/kubernetes-label-selector"], k8s);
174 | } else if (entity?.metadata?.annotations?.["backstage.io/kubernetes-namespace"]) {
175 | pipelineRunsPromise = getPipelineRunsForCluster(tmpCluster.name, tektonApi, entity.metadata.annotations["backstage.io/kubernetes-namespace"], k8s);
176 | } else {
177 | pipelineRunsPromise = getPipelineRunsForCluster(tmpCluster.name, tektonApi, "", k8s);
178 | }
179 | } else {
180 | const tektonApi = "v1";
181 | if (entity?.metadata?.annotations?.["backstage.io/kubernetes-label-selector"]) {
182 | pipelineRunsPromise = getPipelineRunsForCluster(tmpCluster.name, tektonApi, entity.metadata.annotations["backstage.io/kubernetes-label-selector"], k8s);
183 | } else if (entity?.metadata?.annotations?.["backstage.io/kubernetes-namespace"]) {
184 | pipelineRunsPromise = getPipelineRunsForCluster(tmpCluster.name, tektonApi, entity.metadata.annotations["backstage.io/kubernetes-namespace"], k8s);
185 | } else {
186 | pipelineRunsPromise = getPipelineRunsForCluster(tmpCluster.name, tektonApi, "", k8s);
187 | }
188 | }
189 |
190 |
191 | // get all taskruns
192 | let taskRunsPromise: Promise;
193 | if (entity?.metadata?.annotations?.["tektonci/api"]) {
194 | const tektonApi = entity?.metadata?.annotations?.["tektonci/api"];
195 | if (entity?.metadata?.annotations?.["backstage.io/kubernetes-label-selector"]) {
196 | taskRunsPromise = getTaskRunsForCluster(tmpCluster.name, tektonApi, entity.metadata.annotations["backstage.io/kubernetes-label-selector"], k8s);
197 | } else if (entity?.metadata?.annotations?.["backstage.io/kubernetes-namespace"]) {
198 | taskRunsPromise = getTaskRunsForCluster(tmpCluster.name, tektonApi, entity.metadata.annotations["backstage.io/kubernetes-namespace"], k8s);
199 | } else {
200 | taskRunsPromise = getTaskRunsForCluster(tmpCluster.name, tektonApi, "", k8s);
201 | }
202 | } else {
203 | const tektonApi = "v1";
204 | if (entity?.metadata?.annotations?.["backstage.io/kubernetes-label-selector"]) {
205 | taskRunsPromise = getTaskRunsForCluster(tmpCluster.name, tektonApi, entity.metadata.annotations["backstage.io/kubernetes-label-selector"], k8s);
206 | } else if (entity?.metadata?.annotations?.["backstage.io/kubernetes-namespace"]) {
207 | taskRunsPromise = getTaskRunsForCluster(tmpCluster.name, tektonApi, entity.metadata.annotations["backstage.io/kubernetes-namespace"], k8s);
208 | } else {
209 | taskRunsPromise = getTaskRunsForCluster(tmpCluster.name, tektonApi, "", k8s);
210 | }
211 | }
212 |
213 | const [allPipelineRuns, allTaskRuns] = await Promise.all([pipelineRunsPromise, taskRunsPromise]);
214 | tmpCluster.pipelineRuns = allPipelineRuns;
215 |
216 | for (const pipelineRun of tmpCluster.pipelineRuns) {
217 | const dashboardAnnotation = "tektonci." + tmpCluster.name + "/dashboard";
218 | // set dashboardUrl
219 | if (entity.metadata.annotations?.[dashboardAnnotation] !== undefined) {
220 | const dashboardUrl = entity.metadata.annotations[dashboardAnnotation] ?? "";
221 | const replacedUrl = dashboardUrl
222 | .replace(/\$namespace/g, encodeURIComponent(pipelineRun.metadata.namespace))
223 | .replace(/\$pipelinerun/g, encodeURIComponent(pipelineRun.metadata.name));
224 | pipelineRun.pipelineRunDashboardUrl = replacedUrl;
225 | }
226 | // assign the taskruns to the relevant pipelineruns
227 | const taskRunsForPipelineRun: Array = [];
228 | for (const taskRun of allTaskRuns) {
229 | const pipelineRunNameLabel = taskRun.metadata.labels['tekton.dev/pipelineRun'];
230 | if ((String(pipelineRunNameLabel) === pipelineRun.metadata.name) && (taskRun.apiVersion === pipelineRun.apiVersion)) {
231 | taskRunsForPipelineRun.push(taskRun)
232 | }
233 | }
234 | // sort the taskruns associated to a pipelinerun based on start time
235 | taskRunsForPipelineRun.sort((taskRunA, taskRunB) =>
236 | (taskRunA?.status?.startTime ?? 0) > (taskRunB?.status?.startTime ?? 0) ? -1 : 1
237 | );
238 | pipelineRun.taskRuns = taskRunsForPipelineRun;
239 | }
240 |
241 | // sort the pipelineruns
242 | tmpCluster.pipelineRuns.sort((pipelineA, pipelineB) =>
243 | (pipelineA?.status?.startTime ?? 0) > (pipelineB?.status?.startTime ?? 0) ? -1 : 1
244 | );
245 |
246 | updatedClusters[selectedClusterIndex] = {
247 | ...updatedClusters[selectedClusterIndex],
248 | pipelineRuns: tmpCluster.pipelineRuns,
249 | };
250 |
251 | setClusters(updatedClusters);
252 | setFetchingData(false);
253 | } else if (loadedCluster && loadedCluster.errors.length > 0) {
254 | const tmpCluster = {} as Cluster;
255 | tmpCluster.name = loadedCluster.cluster.name;
256 | tmpCluster.error = loadedCluster.errors[0].errorType; // + ":" + JSON.stringify(kubernetesObject.errors[0], null, 2);
257 | updatedClusters[selectedClusterIndex] = tmpCluster;
258 | setClusters(updatedClusters);
259 | setFetchingData(false);
260 | } else {
261 | updatedClusters[selectedClusterIndex] = {
262 | ...updatedClusters[selectedClusterIndex],
263 | pipelineRuns: [],
264 | };
265 | setClusters(updatedClusters);
266 | }
267 | };
268 | fetchTaskRuns()
269 | }
270 |
271 | //}, [kubernetesObjects?.items.find(obj => obj.cluster.name === selectedCluster), selectedCluster]);
272 | }, [kubernetesObjects, selectedCluster]);
273 |
274 | const handleClusterChange = (event: React.ChangeEvent<{ value: unknown }>) => {
275 | if (selectedCluster !== null) {
276 | setFetchingData(true);
277 | }
278 | setSelectedCluster(event.target.value as string);
279 | };
280 |
281 | return (
282 |
283 |
284 | {loading ? (
285 |
288 | ) : (
289 | clusters?.length > 0 && (
290 |
291 |
292 |
293 | Select Cluster
294 |
306 |
307 |
308 |
309 |
310 | {fetchingData && selectedCluster && (
311 |
314 | )
315 | }
316 |
317 |
318 | {!fetchingData && selectedCluster && kubernetesObjects?.items !== undefined && (
319 |
320 |
321 | {clusters
322 | .filter((cluster) => cluster.name === selectedCluster)
323 | .map((cluster) => {
324 | if (cluster.error) {
325 | return (
326 |
327 | {`Error fetching data for cluster ${cluster.name}: ${cluster.error}`}
328 |
329 | );
330 | } else if (
331 | cluster.pipelineRuns !== undefined &&
332 | cluster.pipelineRuns !== null &&
333 | cluster.pipelineRuns.length > 0
334 | ) {
335 | return (
336 |
341 | );
342 | } else if (!fetchingData && (cluster.pipelineRuns === undefined || cluster.pipelineRuns.length === 0)) {
343 | return (
344 |
345 | No pipeline runs for the selected cluster.
346 |
347 | );
348 | } else {
349 | return null;
350 | }
351 | })}
352 |
353 | )}
354 |
355 | )
356 | )}
357 | {error !== undefined && (
358 |
359 |
360 | ;
361 |
362 |
363 | )}
364 |
365 |
366 | );
367 | }
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/TektonDashboard/__fixtures__/cluster.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Cluster1",
3 | "pipelineRuns": [
4 | {
5 | "metadata": {
6 | "labels": {
7 | "testKey": {
8 | "key": "test-key",
9 | "value": "test-value"
10 | }
11 | },
12 | "name": "feature-added-catalog-info-xdjk9",
13 | "namespace": "sample-go-application-build"
14 | },
15 | "pipelineRunDashboardUrl": "https://mock.dashboard",
16 | "taskRuns": [
17 | {
18 | "metadata": {
19 | "name": "taskrun-1",
20 | "namespace": "sample-go-application-build",
21 | "labels": {
22 | "testKey": {
23 | "key": "test-key",
24 | "value": "test-value"
25 | }
26 | }
27 | },
28 | "status": {
29 | "podName": "taskrun-1-pod",
30 | "startTime": "2022-10-25T18:42:30.000Z",
31 | "completionTime": "2022-10-25T18:47:30.000Z",
32 | "duration": 5,
33 | "durationString": "5m",
34 | "steps": [
35 | {
36 | "container": "taskrun-container",
37 | "log": "some log",
38 | "name": "taskrun-name",
39 | "terminated": {
40 | "duration": 5,
41 | "durationString": "5m",
42 | "finishedAt": "2022-10-25T18:47:30.000Z",
43 | "reason": "Completed",
44 | "startedAt": "2022-10-25T18:42:30.000Z"
45 | }
46 | }
47 | ],
48 | "conditions": [
49 | {
50 | "message": "Tasks Completed: 4 (Failed: 0, Cancelled 0), Skipped: 1",
51 | "reason": "Completed",
52 | "status": "True",
53 | "type": "Succeeded"
54 | }
55 | ]
56 | }
57 | },
58 | {
59 | "metadata": {
60 | "name": "taskrun-2",
61 | "namespace": "sample-go-application-build",
62 | "labels": {
63 | "testKey": {
64 | "key": "test-key",
65 | "value": "test-value"
66 | }
67 | }
68 | },
69 | "status": {
70 | "podName": "taskrun-2-pod",
71 | "startTime": "2022-10-25T18:42:30.000Z",
72 | "completionTime": "2022-10-25T18:47:30.000Z",
73 | "duration": 5,
74 | "durationString": "5m",
75 | "steps": [
76 | {
77 | "container": "taskrun-clone",
78 | "log": "clone",
79 | "name": "taskrun-name",
80 | "terminated": {
81 | "duration": 5,
82 | "durationString": "5m",
83 | "finishedAt": "2022-10-25T18:47:30.000Z",
84 | "reason": "Completed",
85 | "startedAt": "2022-10-25T18:42:30.000Z"
86 | }
87 | },
88 | {
89 | "container": "taskrun-build",
90 | "log": "clone",
91 | "name": "build",
92 | "terminated": {
93 | "duration": 5,
94 | "durationString": "5m",
95 | "finishedAt": "2022-10-25T18:47:30.000Z",
96 | "reason": "Completed",
97 | "startedAt": "2022-10-25T18:42:30.000Z"
98 | }
99 | },
100 | {
101 | "container": "taskrun-test",
102 | "log": "test",
103 | "name": "test",
104 | "terminated": {
105 | "duration": 5,
106 | "durationString": "5m",
107 | "finishedAt": "2022-10-25T18:47:30.000Z",
108 | "reason": "Completed",
109 | "startedAt": "2022-10-25T18:42:30.000Z"
110 | }
111 | }
112 | ],
113 | "conditions": [
114 | {
115 | "message": "Tasks Completed: 4 (Failed: 0, Cancelled 0), Skipped: 1",
116 | "reason": "Completed",
117 | "status": "True",
118 | "type": "Succeeded"
119 | }
120 | ]
121 | }
122 | }
123 | ],
124 | "status": {
125 | "startTime": "2022-10-25T18:42:30.000Z",
126 | "completionTime": "2022-10-25T18:47:30.000Z",
127 | "duration": 5,
128 | "durationString": "5m",
129 | "conditions": [
130 | {
131 | "message": "Tasks Completed: 4 (Failed: 0, Cancelled 0), Skipped: 1",
132 | "reason": "Completed",
133 | "status": "True",
134 | "type": "Succeeded"
135 | }
136 | ]
137 | }
138 | }
139 | ]
140 | }
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/TektonDashboard/__fixtures__/pipelinerun.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "labels": {
4 | "testKey": {
5 | "key": "test-key",
6 | "value": "test-value"
7 | }
8 | },
9 | "name": "feature-added-catalog-info-xdjk9",
10 | "namespace": "sample-go-application-build"
11 | },
12 | "pipelineRunDashboardUrl": "https://mock.dashboard",
13 | "taskRuns": [
14 | {
15 | "metadata": {
16 | "name": "taskrun-1",
17 | "namespace": "sample-go-application-build",
18 | "labels": {
19 | "testKey": {
20 | "key": "test-key",
21 | "value": "test-value"
22 | }
23 | }
24 | },
25 | "status": {
26 | "podName": "taskrun-1-pod",
27 | "startTime": "2022-10-25T18:42:30.000Z",
28 | "completionTime": "2022-10-25T18:47:30.000Z",
29 | "duration": 5,
30 | "durationString": "5m",
31 | "steps": [
32 | {
33 | "container": "taskrun-container",
34 | "log": "some log",
35 | "name": "taskrun-name",
36 | "terminated": {
37 | "duration": 5,
38 | "durationString": "5m",
39 | "finishedAt": "2022-10-25T18:47:30.000Z",
40 | "reason": "Completed",
41 | "startedAt": "2022-10-25T18:42:30.000Z"
42 | }
43 | }
44 | ],
45 | "conditions": [
46 | {
47 | "message": "Tasks Completed: 4 (Failed: 0, Cancelled 0), Skipped: 1",
48 | "reason": "Completed",
49 | "status": "True",
50 | "type": "Succeeded"
51 | }
52 | ]
53 | }
54 | },
55 | {
56 | "metadata": {
57 | "name": "taskrun-2",
58 | "namespace": "sample-go-application-build",
59 | "labels": {
60 | "testKey": {
61 | "key": "test-key",
62 | "value": "test-value"
63 | }
64 | }
65 | },
66 | "status": {
67 | "podName": "taskrun-2-pod",
68 | "startTime": "2022-10-25T18:42:30.000Z",
69 | "completionTime": "2022-10-25T18:47:30.000Z",
70 | "duration": 5,
71 | "durationString": "5m",
72 | "steps": [
73 | {
74 | "container": "taskrun-clone",
75 | "log": "clone",
76 | "name": "taskrun-name",
77 | "terminated": {
78 | "duration": 5,
79 | "durationString": "5m",
80 | "finishedAt": "2022-10-25T18:47:30.000Z",
81 | "reason": "Completed",
82 | "startedAt": "2022-10-25T18:42:30.000Z"
83 | }
84 | },
85 | {
86 | "container": "taskrun-build",
87 | "log": "clone",
88 | "name": "build",
89 | "terminated": {
90 | "duration": 5,
91 | "durationString": "5m",
92 | "finishedAt": "2022-10-25T18:47:30.000Z",
93 | "reason": "Completed",
94 | "startedAt": "2022-10-25T18:42:30.000Z"
95 | }
96 | },
97 | {
98 | "container": "taskrun-test",
99 | "log": "test",
100 | "name": "test",
101 | "terminated": {
102 | "duration": 5,
103 | "durationString": "5m",
104 | "finishedAt": "2022-10-25T18:47:30.000Z",
105 | "reason": "Completed",
106 | "startedAt": "2022-10-25T18:42:30.000Z"
107 | }
108 | }
109 | ],
110 | "conditions": [
111 | {
112 | "message": "Tasks Completed: 4 (Failed: 0, Cancelled 0), Skipped: 1",
113 | "reason": "Completed",
114 | "status": "True",
115 | "type": "Succeeded"
116 | }
117 | ]
118 | }
119 | }
120 | ],
121 | "status": {
122 | "startTime": "2022-10-25T18:42:30.000Z",
123 | "completionTime": "2022-10-25T18:47:30.000Z",
124 | "duration": 5,
125 | "durationString": "5m",
126 | "conditions": [
127 | {
128 | "message": "Tasks Completed: 4 (Failed: 0, Cancelled 0), Skipped: 1",
129 | "reason": "Completed",
130 | "status": "True",
131 | "type": "Succeeded"
132 | }
133 | ]
134 | }
135 | }
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/components/TektonDashboard/index.ts:
--------------------------------------------------------------------------------
1 | export { TektonDashboardComponent } from './TektoDashboardComponent';
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/index.ts:
--------------------------------------------------------------------------------
1 | export { tektonPipelinesPluginPlugin, EntityTektonPipelinesContent } from './plugin';
2 | export { isTektonCiAvailable } from './Router';
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/plugin.test.ts:
--------------------------------------------------------------------------------
1 | import { tektonPipelinesPluginPlugin } from './plugin';
2 |
3 | describe('tekton-pipelines-plugin', () => {
4 | it('should export plugin', () => {
5 | expect(tektonPipelinesPluginPlugin).toBeDefined();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import { createPlugin, createRoutableExtension } from '@backstage/core-plugin-api';
2 |
3 | import { rootRouteRef } from './routes';
4 |
5 | export const tektonPipelinesPluginPlugin = createPlugin({
6 | id: 'tekton-pipelines',
7 | routes: {
8 | root: rootRouteRef,
9 | },
10 | });
11 |
12 | export type EntityTektonPipelinesContentProps = {
13 | /**
14 | * Sets the refresh interval in milliseconds. The default value is 10000 (10 seconds)
15 | */
16 | refreshIntervalMs?: number;
17 | };
18 |
19 | export const EntityTektonPipelinesContent: (
20 | props: EntityTektonPipelinesContentProps,
21 | ) => JSX.Element = tektonPipelinesPluginPlugin.provide(
22 | createRoutableExtension({
23 | name: 'EntityTektonPipelinesContent',
24 | component: () => import('./Router').then(m => m.Router),
25 | mountPoint: rootRouteRef,
26 | }),
27 | );
28 |
29 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/routes.ts:
--------------------------------------------------------------------------------
1 | import { createRouteRef } from '@backstage/core-plugin-api';
2 |
3 | export const rootRouteRef = createRouteRef({
4 | id: 'tekton-pipelines',
5 | });
6 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 |
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types';
--------------------------------------------------------------------------------
/plugins/tekton-pipelines/src/types/types.ts:
--------------------------------------------------------------------------------
1 | export interface Cluster {
2 | name: string;
3 | pipelineRuns: PipelineRun[];
4 | error: string;
5 | }
6 |
7 | export interface PipelineRun {
8 | apiVersion: string;
9 | kind: string;
10 | metadata: {
11 | name: string;
12 | namespace: string;
13 | labels: Record;
14 | };
15 | pipelineRunDashboardUrl: string;
16 | taskRuns: Array;
17 | status: {
18 | childReferences: Array
19 | conditions: [Condition];
20 | startTime: string;
21 | completionTime: string;
22 | duration: number;
23 | durationString: string;
24 | };
25 | }
26 |
27 | export interface ChildReferences {
28 | apiVersion: string;
29 | kind: string;
30 | name: string;
31 | pipelineTaskName: string;
32 | }
33 |
34 | export interface TaskRun {
35 | apiVersion: string;
36 | kind: string;
37 | metadata: {
38 | name: string;
39 | namespace: string;
40 | labels: Record;
41 | };
42 | status: {
43 | conditions: [Condition];
44 | podName: string;
45 | steps: Array;
46 | startTime: string;
47 | completionTime: string;
48 | duration: number;
49 | durationString: string;
50 | };
51 | }
52 |
53 | export interface Label {
54 | key: string;
55 | value: string;
56 | }
57 |
58 | export interface Step {
59 | container: string;
60 | name: string;
61 | terminated: Terminated;
62 | log: string;
63 | }
64 |
65 | export interface Terminated {
66 | startedAt: string;
67 | finishedAt: string;
68 | duration: number;
69 | durationString: string;
70 | reason: string;
71 | }
72 |
73 | export interface Condition {
74 | reason: string;
75 | type: string;
76 | status: string;
77 | message: string;
78 | }
79 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@backstage/cli/config/tsconfig.json",
3 | "include": [
4 | "packages/*/src",
5 | "plugins/*/src",
6 | "plugins/*/dev",
7 | "plugins/*/migrations"
8 | ],
9 | "exclude": ["node_modules"],
10 | "compilerOptions": {
11 | "outDir": "dist-types",
12 | "rootDir": "."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------