Compare commits
No commits in common. "development" and "build" have entirely different histories.
developmen
...
build
83 changed files with 9752 additions and 40430 deletions
|
@ -1,6 +1,10 @@
|
|||
dist-types
|
||||
.git
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
node_modules
|
||||
packages/*/dist
|
||||
packages/*/src
|
||||
packages/*/node_modules
|
||||
plugins/*/dist
|
||||
plugins/*/node_modules
|
||||
plugins
|
||||
*.local.yaml
|
||||
github-integration.yaml
|
||||
k8s-config.yaml
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
playwright.config.ts
|
51
.github/workflows/build-and-push.yaml
vendored
51
.github/workflows/build-and-push.yaml
vendored
|
@ -1,51 +0,0 @@
|
|||
name: ci
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Repository meta
|
||||
id: repository
|
||||
run: |
|
||||
registry=${{ github.server_url }}
|
||||
registry=${registry##http*://}
|
||||
echo "registry=${registry}" >> "$GITHUB_OUTPUT"
|
||||
echo "registry=${registry}"
|
||||
repository="$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
|
||||
echo "repository=${repository}" >> "$GITHUB_OUTPUT"
|
||||
echo "repository=${repository}"
|
||||
-
|
||||
name: Docker meta
|
||||
uses: docker/metadata-action@v5
|
||||
id: docker
|
||||
with:
|
||||
images: ${{ steps.repository.outputs.registry }}/${{ steps.repository.outputs.repository }}
|
||||
-
|
||||
name: Login to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ steps.repository.outputs.registry }}
|
||||
username: ${{ secrets.PACKAGES_USER }}
|
||||
password: ${{ secrets.PACKAGES_TOKEN }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
buildkitd-flags: '--allow-insecure-entitlement network.host'
|
||||
driver-opts: network=host
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
allow: network.host
|
||||
network: host
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.docker.outputs.tags }}
|
22
.github/workflows/dependabot-automerge.yaml
vendored
Normal file
22
.github/workflows/dependabot-automerge.yaml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: Dependabot auto-merge
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ main ]
|
||||
types: [ opened ]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
enableAutoMerge:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.user.login == 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Enable auto-merge for Dependabot PRs
|
||||
run: gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -27,6 +27,9 @@ node_modules/
|
|||
# Node version directives
|
||||
.nvmrc
|
||||
|
||||
# NPM config files
|
||||
.npmrc
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
@ -50,5 +53,5 @@ site
|
|||
# vscode database functionality support files
|
||||
*.session.sql
|
||||
|
||||
# E2E test reports
|
||||
e2e-test-report/
|
||||
# JetBrains
|
||||
.idea
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
nodeLinker: node-modules
|
94
Dockerfile
94
Dockerfile
|
@ -1,94 +0,0 @@
|
|||
# Stage 1 - Create yarn install skeleton layer
|
||||
FROM node:20.18.1 AS packages
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
COPY packages packages
|
||||
|
||||
# Comment this out if you don't have any internal plugins
|
||||
COPY plugins plugins
|
||||
|
||||
RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+
|
||||
|
||||
# Stage 2 - Install dependencies and build packages
|
||||
FROM node:20.18.1 AS build
|
||||
|
||||
# Required for arm64
|
||||
RUN apt update -y
|
||||
RUN apt install -y python3 make gcc build-essential bash
|
||||
|
||||
USER node
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=packages --chown=node:node /app .
|
||||
|
||||
RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
|
||||
yarn install --network-timeout 600000
|
||||
|
||||
COPY --chown=node:node . .
|
||||
|
||||
RUN yarn tsc
|
||||
RUN yarn --cwd packages/backend build
|
||||
# If you have not yet migrated to package roles, use the following command instead:
|
||||
# RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies
|
||||
|
||||
RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \
|
||||
&& tar xzf packages/backend/dist/skeleton.tar.gz -C packages/backend/dist/skeleton \
|
||||
&& tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle
|
||||
|
||||
# Stage 3 - Build the actual backend image and install production dependencies
|
||||
FROM node:20.18.1
|
||||
|
||||
# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
|
||||
# Install packages needed to get utility binaries
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends python3 python3-pip python3-venv g++ build-essential ca-certificates curl
|
||||
|
||||
RUN yarn config set python /usr/bin/python3
|
||||
|
||||
# Add kubectl for the kube apply plugin.
|
||||
# Add mkdocs for the TechDocs plugin.
|
||||
RUN if test "$(uname -m)" = "x86_64"; \
|
||||
then \
|
||||
curl -L -o /usr/local/bin/kubectl https://dl.k8s.io/release/v1.29.9/bin/linux/amd64/kubectl; \
|
||||
fi
|
||||
RUN if test "$(uname -m)" != "x86_64"; \
|
||||
then \
|
||||
curl -L -o /usr/local/bin/kubectl https://dl.k8s.io/release/v1.29.9/bin/linux/arm64/kubectl; \
|
||||
fi
|
||||
RUN chmod +x /usr/local/bin/kubectl
|
||||
|
||||
ENV VIRTUAL_ENV=/opt/venv
|
||||
RUN python3 -m venv $VIRTUAL_ENV
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN pip3 install 'mkdocs-techdocs-core==1.4.2' 'mkdocs-awesome-pages-plugin==2.10.1'
|
||||
|
||||
# From here on we use the least-privileged `node` user to run the backend.
|
||||
USER node
|
||||
|
||||
# This should create the app dir as `node`.
|
||||
# If it is instead created as `root` then the `tar` command below will
|
||||
# fail: `can't create directory 'packages/': Permission denied`.
|
||||
# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`)
|
||||
# so the app dir is correctly created as `node`.
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the install dependencies from the build stage and context
|
||||
COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./
|
||||
|
||||
RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
|
||||
yarn install --production --network-timeout 600000
|
||||
|
||||
# Copy the built packages from the build stage
|
||||
COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./
|
||||
|
||||
# Copy any other files that we need at runtime
|
||||
COPY --chown=node:node app-config.yaml ./
|
||||
|
||||
# This switches many Node.js dependencies to production mode.
|
||||
ENV NODE_ENV production
|
||||
|
||||
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
|
202
LICENSE
Normal file
202
LICENSE
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
116
README.md
116
README.md
|
@ -1,116 +1,10 @@
|
|||
# EDP Backstage
|
||||
# [Backstage](https://backstage.io)
|
||||
|
||||
The EDP bespoke version of backstage.
|
||||
This is your newly scaffolded Backstage App, Good Luck!
|
||||
|
||||
With respect to the CNOE stack (where eDF originates from) it is comparable to https://github.com/cnoe-io/backstage-app
|
||||
To start the app, run:
|
||||
|
||||
At the time writing CNOE-backstage-app is "version": "1.28.4"
|
||||
|
||||
## Container Images
|
||||
|
||||
Container images are pushed to the Cefor Container Registry and available [here](https://forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/DevFW-CICD/-/packages/container/backstage-edp/).
|
||||
|
||||
|
||||
## Local Development
|
||||
|
||||
Use of [**edpbuilder**](https://forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/DevFW/edpbuilder.git) is recommended for local setup.
|
||||
|
||||
### Create your local cluster
|
||||
|
||||
Once edpbuilder is installed on your computer, create a stack that you are interested in. For example:
|
||||
|
||||
> Hint: From here on this is the old CNOE README .... no guarantee that this works as described!
|
||||
|
||||
### Update Backstage application config
|
||||
|
||||
Once all ArgoCD applications are healthy, you need to update a few fields in the [app-config.yaml](./app-config.yaml) file.
|
||||
|
||||
#### Update control plane URL
|
||||
|
||||
The control plane port must be updated every time a cluster is created. Run the `kubectl cluster-info` command to get the control plane URL. Once you have your URL, update your `app-config.yaml` file at [this line](https://github.com/cnoe-io/backstage-app/blob/9ee3514e51c1a354b7fe85a90117faf8328bfa0b/app-config.yaml#L122).
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ kubectl cluster-info
|
||||
|
||||
Kubernetes control plane is running at https://127.0.0.1:36463
|
||||
CoreDNS is running at https://127.0.0.1:36463/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
|
||||
```
|
||||
|
||||
For this particular example output, the `https://127.0.0.1:36463` above is the URL you need to use in your `app-config.yaml`.
|
||||
|
||||
#### Update service account token
|
||||
|
||||
Since tokens are generated each time the backstage service account is created, you need to update this value as well. The command to retrieve the service account token is:
|
||||
|
||||
`kubectl -n backstage exec -it deploy/backstage -- cat /var/run/secrets/kubernetes.io/serviceaccount/token`
|
||||
|
||||
Copy the token value and updated the app-config file at [this line](https://github.com/cnoe-io/backstage-app/blob/main/app-config.yaml#L127).
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ kubectl -n backstage exec -it deploy/backstage -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
|
||||
eyJhbGciOiJSUzI1NiIsImtpZCI6IkRxbDRCSnNicjFwekFqdmxwNDc5MHJqeUlFSjhxNHU0LV95OC1s...
|
||||
```
|
||||
|
||||
If you do not want to place the token value in your file, you can use environment variables instead:
|
||||
1. Set [this line](https://github.com/cnoe-io/backstage-app/blob/main/app-config.yaml#L127) value to be `${BACKSTAGE_SA_TOKEN}`.
|
||||
2. Then export the token value:
|
||||
```bash
|
||||
export BACKSTAGE_SA_TOKEN=$(kubectl -n backstage exec -it deploy/backstage -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
||||
```
|
||||
|
||||
#### Update ArgoCD token
|
||||
|
||||
ArgoCD admin passwords are generated on each fresh installation. You need to update the configuration file accordingly. To obtain your password, run: `./idpbuilder get secrets -p argocd`. Then update [this line](https://github.com/cnoe-io/backstage-app/blob/9ee3514e51c1a354b7fe85a90117faf8328bfa0b/app-config.yaml#L136)
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ ./idpbuilder get secrets -p argocd
|
||||
|
||||
---------------------------
|
||||
Name: argocd-initial-admin-secret
|
||||
Namespace: argocd
|
||||
Data:
|
||||
password : abc
|
||||
username : admin
|
||||
```
|
||||
|
||||
#### Update Gitea Credentials
|
||||
|
||||
Gitea admin passwords are generated on each fresh installation as well. To obtain your password, run: `./idpbuilder get secrets -p argocd`.
|
||||
Then update [this line](https://github.com/cnoe-io/backstage-app/blob/9ee3514e51c1a354b7fe85a90117faf8328bfa0b/app-config.yaml#L40) and [this line](https://github.com/cnoe-io/backstage-app/blob/9ee3514e51c1a354b7fe85a90117faf8328bfa0b/app-config.yaml#L44).
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ ./idpbuilder get secrets -p gitea
|
||||
|
||||
---------------------------
|
||||
Name: gitea-credential
|
||||
Namespace: gitea
|
||||
Data:
|
||||
password : abc
|
||||
username : giteaAdmin
|
||||
````
|
||||
|
||||
### Start Backstage processes
|
||||
|
||||
Once the `app-config.yaml` file is updated, you are ready to start your backstage instance. For development purposes, using two terminal windows or tabs is recommended. You can also run them through your favorite IDE.
|
||||
|
||||
In the first terminal tab, install dependencies and start the backend.
|
||||
|
||||
```bash
|
||||
```sh
|
||||
yarn install
|
||||
yarn run start-backend
|
||||
```
|
||||
|
||||
In the first terminal tab, run the frontend.
|
||||
|
||||
```bash
|
||||
yarn run start
|
||||
yarn dev
|
||||
```
|
||||
|
|
|
@ -30,6 +30,6 @@ backend:
|
|||
|
||||
catalog:
|
||||
# Overrides the default list locations from app-config.yaml as these contain example data.
|
||||
# See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details
|
||||
# See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details
|
||||
# on how to get entities into the catalog.
|
||||
locations: []
|
||||
|
|
|
@ -7,7 +7,7 @@ organization:
|
|||
|
||||
backend:
|
||||
# Used for enabling authentication, secret is shared by all backend plugins
|
||||
# See https://backstage.io/docs/auth/service-to-service-auth for
|
||||
# See https://backstage.io/docs/tutorials/backend-to-backend-auth for
|
||||
# information on the format
|
||||
# auth:
|
||||
# keys:
|
||||
|
@ -30,25 +30,26 @@ backend:
|
|||
database:
|
||||
client: better-sqlite3
|
||||
connection: ':memory:'
|
||||
cache:
|
||||
store: memory
|
||||
# workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
|
||||
integrations:
|
||||
gitea:
|
||||
- baseUrl: https://cnoe.localtest.me:8443/gitea
|
||||
host: cnoe.localtest.me:8443
|
||||
username: giteaAdmin
|
||||
password: ${GITEA_PASSWORD}
|
||||
- baseUrl: https://cnoe.localtest.me/gitea
|
||||
host: cnoe.localtest.me
|
||||
username: giteaAdmin
|
||||
password: ${GITEA_PASSWORD}
|
||||
|
||||
integrations: {}
|
||||
# - host: github.com
|
||||
# # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information
|
||||
# # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration
|
||||
# token: ${GITHUB_TOKEN}
|
||||
### Example for how to add your GitHub Enterprise instance using the API:
|
||||
# - host: ghe.example.net
|
||||
# apiBaseUrl: https://ghe.example.net/api/v3
|
||||
# token: ${GHE_TOKEN}
|
||||
|
||||
proxy:
|
||||
### Example for how to add a proxy endpoint for the frontend.
|
||||
### A typical reason to do this is to handle HTTPS and CORS for internal services.
|
||||
# endpoints:
|
||||
# '/test':
|
||||
# target: 'https://example.com'
|
||||
# changeOrigin: true
|
||||
# '/test':
|
||||
# target: 'https://example.com'
|
||||
# changeOrigin: true
|
||||
|
||||
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
|
||||
# Note: After experimenting with basic setup, use CI/CD to generate docs
|
||||
|
@ -60,76 +61,25 @@ techdocs:
|
|||
runIn: 'docker' # Alternatives - 'local'
|
||||
publisher:
|
||||
type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
|
||||
|
||||
auth:
|
||||
# see https://backstage.io/docs/auth/ to learn about auth providers
|
||||
environment: local # set this to development to enable SSO
|
||||
session:
|
||||
secret: abcdfkjalskdfjkla
|
||||
providers:
|
||||
guest: {}
|
||||
keycloak-oidc:
|
||||
development:
|
||||
metadataUrl: https://cnoe.localtest.me:8443/keycloak/realms/cnoe/.well-known/openid-configuration
|
||||
clientId: backstage
|
||||
clientSecret: ${KEYCLOAK_CLIENT_SECRET}
|
||||
prompt: auto
|
||||
environment: local
|
||||
providers: {}
|
||||
|
||||
scaffolder:
|
||||
# see https://backstage.io/docs/features/software-templates/configuration for software template options
|
||||
defaultAuthor:
|
||||
name: backstage-scaffolder
|
||||
email: noreply
|
||||
defaultCommitMessage: "backstage scaffolder"
|
||||
|
||||
catalog:
|
||||
import:
|
||||
entityFilename: catalog-info.yaml
|
||||
pullRequestBranchName: backstage-integration
|
||||
rules:
|
||||
- allow: [ Component, System, API, Resource, Location, Template ]
|
||||
locations:
|
||||
- type: url
|
||||
target: https://cnoe.localtest.me:8443/gitea/giteaAdmin/idpbuilder-localdev-backstage-templates-entities/src/branch/main/catalog-info.yaml
|
||||
# # Local example template
|
||||
# - type: file
|
||||
# target: ../../examples/template/template.yaml
|
||||
# rules:
|
||||
# - allow: [Template]
|
||||
#
|
||||
# # Local example organizational data
|
||||
# - type: file
|
||||
# target: ../../examples/org.yaml
|
||||
# rules:
|
||||
# - allow: [User, Group]
|
||||
- allow: [Component, System, API, Resource, Location, Template]
|
||||
locations: []
|
||||
|
||||
## Uncomment these lines to add more example data
|
||||
# - type: url
|
||||
# target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml
|
||||
|
||||
## Uncomment these lines to add an example org
|
||||
# - type: url
|
||||
# target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml
|
||||
# rules:
|
||||
# - allow: [User, Group]
|
||||
kubernetes:
|
||||
serviceLocatorMethod:
|
||||
type: 'multiTenant'
|
||||
clusterLocatorMethods:
|
||||
- type: 'config'
|
||||
clusters:
|
||||
- url: https://127.0.0.1:33277 # you may need to change this
|
||||
name: local
|
||||
authProvider: 'serviceAccount'
|
||||
skipTLSVerify: true
|
||||
# replace with your own service account token value. e.g. kubectl -n backstage exec -it deploy/backstage -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
serviceAccountToken: eyJhbG......
|
||||
argocd:
|
||||
appLocatorMethods:
|
||||
- type: 'config'
|
||||
instances:
|
||||
- name: local
|
||||
url: https://cnoe.localtest.me:8443/argocd
|
||||
username: admin
|
||||
# replace with your argocd password e.g. kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
|
||||
password: ${ARGOCD_ADMIN_PASSWORD}
|
||||
argoWorkflows:
|
||||
baseUrl: https://cnoe.localtest.me:8443/argo-workflows
|
||||
clusterLocatorMethods: []
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"version": "1.36.1"
|
||||
"version": "1.16.0"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: backstage-idpbuilder
|
||||
name: backstage
|
||||
description: An example of a Backstage application.
|
||||
# Example for optional annotations
|
||||
# annotations:
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
SERVICE_ACCOUNT_DIR="/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
KUBERNETES_SERVICE_SCHEME=$(case $KUBERNETES_SERVICE_PORT in 80|8080|8081) echo "http";; *) echo "https"; esac)
|
||||
KUBERNETES_SERVER_URL="$KUBERNETES_SERVICE_SCHEME"://"$KUBERNETES_SERVICE_HOST":"$KUBERNETES_SERVICE_PORT"
|
||||
KUBERNETES_CLUSTER_CA_FILE="$SERVICE_ACCOUNT_DIR"/ca.crt
|
||||
KUBERNETES_NAMESPACE=$(cat "$SERVICE_ACCOUNT_DIR"/namespace)
|
||||
KUBERNETES_USER_TOKEN=$(cat "$SERVICE_ACCOUNT_DIR"/token)
|
||||
KUBERNETES_CONTEXT="inCluster"
|
||||
|
||||
rm -rf "$HOME"/.kube
|
||||
mkdir -p "$HOME"/.kube
|
||||
cat << EOF > "$HOME"/.kube/config
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
preferences: {}
|
||||
current-context: $KUBERNETES_CONTEXT
|
||||
clusters:
|
||||
- cluster:
|
||||
server: $KUBERNETES_SERVER_URL
|
||||
certificate-authority: $KUBERNETES_CLUSTER_CA_FILE
|
||||
name: inCluster
|
||||
users:
|
||||
- name: podServiceAccount
|
||||
user:
|
||||
token: $KUBERNETES_USER_TOKEN
|
||||
contexts:
|
||||
- context:
|
||||
cluster: inCluster
|
||||
user: podServiceAccount
|
||||
namespace: $KUBERNETES_NAMESPACE
|
||||
name: $KUBERNETES_CONTEXT
|
||||
EOF
|
||||
|
||||
cnoe-cli "$@"
|
|
@ -1,17 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: game-demo
|
||||
data:
|
||||
# property-like keys; each key maps to a simple value
|
||||
player_initial_lives: "3"
|
||||
ui_properties_file_name: "user-interface.properties"
|
||||
|
||||
# file-like keys
|
||||
game.properties: |
|
||||
enemy.types=aliens,monsters
|
||||
player.maximum-lives=5
|
||||
user-interface.properties: |
|
||||
color.good=purple
|
||||
color.bad=yellow
|
||||
allow.textmode=true
|
|
@ -1,41 +0,0 @@
|
|||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
kind: Template
|
||||
metadata:
|
||||
name: deploy-resources-object
|
||||
title: Deploy Resources using object
|
||||
description: Deploy Resource to Kubernetes
|
||||
spec:
|
||||
owner: guest
|
||||
type: service
|
||||
# these are the steps which are rendered in the frontend with the form input
|
||||
parameters: []
|
||||
steps:
|
||||
- id: template
|
||||
name: Generating component
|
||||
action: fetch:template
|
||||
input:
|
||||
url: ./skeleton
|
||||
- id: apply
|
||||
name: apply-manifest
|
||||
action: cnoe:kubernetes:apply
|
||||
input:
|
||||
namespaced: true
|
||||
manifestObject:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: game-demo
|
||||
data:
|
||||
# property-like keys; each key maps to a simple value
|
||||
player_initial_lives: "3"
|
||||
ui_properties_file_name: "user-interface.properties"
|
||||
|
||||
# file-like keys
|
||||
game.properties: |
|
||||
enemy.types=aliens,monsters
|
||||
player.maximum-lives=5
|
||||
user-interface.properties: |
|
||||
color.good=purple
|
||||
color.bad=yellow
|
||||
allow.textmode=true
|
||||
clusterName: local
|
|
@ -1,41 +0,0 @@
|
|||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
kind: Template
|
||||
metadata:
|
||||
name: deploy-resources-string
|
||||
title: Deploy Resources using literal string
|
||||
description: Deploy Resource to Kubernetes
|
||||
spec:
|
||||
owner: guest
|
||||
type: service
|
||||
# these are the steps which are rendered in the frontend with the form input
|
||||
parameters: []
|
||||
steps:
|
||||
- id: template
|
||||
name: Generating component
|
||||
action: fetch:template
|
||||
input:
|
||||
url: ./skeleton
|
||||
- id: apply
|
||||
name: apply-manifest
|
||||
action: cnoe:kubernetes:apply
|
||||
input:
|
||||
namespaced: true
|
||||
manifestString: |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: game-demo
|
||||
data:
|
||||
# property-like keys; each key maps to a simple value
|
||||
player_initial_lives: "3"
|
||||
ui_properties_file_name: "user-interface.properties"
|
||||
|
||||
# file-like keys
|
||||
game.properties: |
|
||||
enemy.types=aliens,monsters
|
||||
player.maximum-lives=5
|
||||
user-interface.properties: |
|
||||
color.good=purple
|
||||
color.bad=yellow
|
||||
allow.textmode=true
|
||||
clusterName: local
|
|
@ -1,30 +0,0 @@
|
|||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
kind: Template
|
||||
metadata:
|
||||
name: deploy-resources
|
||||
title: Deploy Resources
|
||||
description: Deploy Resource to Kubernetes
|
||||
spec:
|
||||
owner: guest
|
||||
type: service
|
||||
# these are the steps which are rendered in the frontend with the form input
|
||||
parameters:
|
||||
- title: file name
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
description: file name
|
||||
default: cm.yaml
|
||||
steps:
|
||||
- id: template
|
||||
name: Generating component
|
||||
action: fetch:template
|
||||
input:
|
||||
url: ./skeleton
|
||||
- id: apply
|
||||
name: apply-manifest
|
||||
action: cnoe:kubernetes:apply
|
||||
input:
|
||||
namespaced: true
|
||||
manifestPath: cm.yaml
|
||||
clusterName: local
|
9
github-integration.yaml
Normal file
9
github-integration.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
appId: 123456
|
||||
webhookUrl: https://somehwere
|
||||
clientId: some.id
|
||||
clientSecret: ""
|
||||
webhookSecret: ""
|
||||
privateKey: |
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
|
||||
-----END RSA PRIVATE KEY-----
|
16
k8s-config.yaml
Normal file
16
k8s-config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
type: 'config'
|
||||
clusters:
|
||||
- url: https://3CEBA3CA7870A3E5BFE2CF3FA173EE56.gr7.us-west-2.eks.amazonaws.com:443
|
||||
name: canoe-packaging
|
||||
authProvider: 'serviceAccount'
|
||||
skipTLSVerify: false
|
||||
skipMetricsLookup: true
|
||||
serviceAccountToken: ""
|
||||
# dashboardUrl: http://127.0.0.1:64713 # url copied from running the command: minikube service kubernetes-dashboard -n kubernetes-dashboard
|
||||
# dashboardApp: standard
|
||||
caData: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1ERXlOVEl3TkRBMU5Wb1hEVE16TURFeU1qSXdOREExTlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTlJvCnU5dkl6cjZmVEk4RThyR0Q2RHNoLzhyK0lkWmFHZGxsUytKbDN0Q2JteTVYUU15NnpOMU5acG1zRHpDTC9nUlIKS0s5WTVhUmRUWjFLdklkekRMQXdMeXpqODk5clJtYjB2aXUzR0ZQdDcxSWFYMEp1VmQwaTBrQit5Y01jSFo2QgpjOGhmMUErM1I2VVpCZDZsaUx0dG5pUjZwb29oYXdobG5DSEN4L1oyd014YWEvU21SUWxDMjhhTEhLZC9ZU0s2CndXS1VOQmVTMmpGZGc5bVVkcnJDREx5MkxqUTNUcUtPVW9PNEQ3bm9rVTh1NUFtejhldWFxdzR4U25ZMExucmsKWVk1MmhvOW5qRnZwOE5WQnE1VjRPUFVXaEhvQXE4TnZjZlVITkNSdWZkN09FZG85Y2t1Q1B3VzFiZWxNOW9oeApURFAvWFlsS09INFVQTDFHeUJFQ0F3RUFBYU5aTUZjd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZOeUgrRTZxb2VMTlVEVkl4ZXpTSjk3STRoZytNQlVHQTFVZEVRUU8KTUF5Q0NtdDFZbVZ5Ym1WMFpYTXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBRDVoeStNaDBRdHJ6dG5vV0tFRgpTaFFsanE1cjJGYUZablAyYU9OWS9uaHNxdThjSmZkbWFyQUtsR1JkRTBocnVoaGprdE55ckdmcEZ5d1ErR0hhClR4d0N6NW9uUEhYaTRNZnBadEpaNzZYSERtT3BFR2diSTFhL0VCQUV2YkxHSVRWT3NTMmQ2MTFKTTF0bkJKRFgKNERFaVc5aXJ1Nm1wR2NaQ1JWYlhUT005cHV1V0NTQ1pPNktKZ29NZlVMbnpHT0diN0ludmtoajBJZThQQ0JGWQpWUmFvRm5NNE5HMUdHMnpuckcrNjFucFlBbGpGcjhQN2J4WmRsWWpPcjFGbFhydU1UeEdEZEpNYkNTcFViRmRUCkxOOVUxYlFNS3JBN3NsZEJCcTc0ZHlUZkNKZDFQaGdMSzZZbVZGdFo3Vmk4eFkwbjlpa2svZEpDWjM5aTFWR2wKK3NzPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
|
||||
# caFile: '' # local path to CA file
|
||||
customResources:
|
||||
- group: 'argoproj.io'
|
||||
apiVersion: 'v1alpha1'
|
||||
plural: 'applications'
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"packages": ["packages/*", "plugins/*"],
|
||||
"npmClient": "yarn",
|
||||
"version": "0.1.0",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
"useWorkspaces": true,
|
||||
"version": "0.1.0"
|
||||
}
|
||||
|
|
18
package.json
18
package.json
|
@ -3,7 +3,7 @@
|
|||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "18 || 20"
|
||||
"node": "16 || 18"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "concurrently \"yarn start\" \"yarn start-backend\"",
|
||||
|
@ -17,8 +17,6 @@
|
|||
"clean": "backstage-cli repo clean",
|
||||
"test": "backstage-cli repo test",
|
||||
"test:all": "backstage-cli repo test --coverage",
|
||||
"test:e2e": "playwright test",
|
||||
"fix": "backstage-cli repo fix",
|
||||
"lint": "backstage-cli repo lint --since origin/main",
|
||||
"lint:all": "backstage-cli repo lint",
|
||||
"prettier:check": "prettier --check .",
|
||||
|
@ -31,19 +29,17 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.30.0",
|
||||
"@backstage/e2e-test-utils": "^0.1.1",
|
||||
"@playwright/test": "^1.32.3",
|
||||
"@backstage/cli": "^0.22.9",
|
||||
"@spotify/prettier-config": "^12.0.0",
|
||||
"concurrently": "^8.0.0",
|
||||
"lerna": "^7.3.0",
|
||||
"concurrently": "^6.0.0",
|
||||
"lerna": "^4.0.0",
|
||||
"node-gyp": "^9.0.0",
|
||||
"prettier": "^2.3.2",
|
||||
"typescript": "~5.2.0"
|
||||
"typescript": "~4.6.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18"
|
||||
"@types/react": "^17",
|
||||
"@types/react-dom": "^17"
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
"lint-staged": {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
public
|
6
packages/app/cypress.json
Normal file
6
packages/app/cypress.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:3001",
|
||||
"fixturesFolder": false,
|
||||
"pluginsFile": false,
|
||||
"retries": 3
|
||||
}
|
12
packages/app/cypress/.eslintrc.json
Normal file
12
packages/app/cypress/.eslintrc.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"plugins": ["cypress"],
|
||||
"extends": ["plugin:cypress/recommended"],
|
||||
"rules": {
|
||||
"jest/expect-expect": [
|
||||
"error",
|
||||
{
|
||||
"assertFunctionNames": ["expect", "cy.contains"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
6
packages/app/cypress/integration/app.js
Normal file
6
packages/app/cypress/integration/app.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
describe('App', () => {
|
||||
it('should render the catalog', () => {
|
||||
cy.visit('/');
|
||||
cy.contains('My Company Catalog');
|
||||
});
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('App should render the welcome page', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.getByText('My Company Catalog')).toBeVisible();
|
||||
});
|
|
@ -11,55 +11,69 @@
|
|||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"test": "backstage-cli package test",
|
||||
"lint": "backstage-cli package lint"
|
||||
"lint": "backstage-cli package lint",
|
||||
"test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev",
|
||||
"test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run",
|
||||
"cy:dev": "cypress open",
|
||||
"cy:run": "cypress run --browser chrome"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage-community/plugin-github-actions": "^0.6.16",
|
||||
"@backstage-community/plugin-tech-radar": "^0.7.4",
|
||||
"@backstage/app-defaults": "^1.5.17",
|
||||
"@backstage/catalog-model": "^1.7.3",
|
||||
"@backstage/cli": "^0.30.0",
|
||||
"@backstage/core-app-api": "^1.15.5",
|
||||
"@backstage/core-components": "^0.16.4",
|
||||
"@backstage/core-plugin-api": "^1.10.4",
|
||||
"@backstage/integration-react": "^1.2.4",
|
||||
"@backstage/plugin-api-docs": "^0.12.4",
|
||||
"@backstage/plugin-catalog": "^1.27.0",
|
||||
"@backstage/plugin-catalog-common": "^1.1.3",
|
||||
"@backstage/plugin-catalog-graph": "^0.4.16",
|
||||
"@backstage/plugin-catalog-import": "^0.12.10",
|
||||
"@backstage/plugin-catalog-react": "^1.15.2",
|
||||
"@backstage/plugin-home": "^0.8.5",
|
||||
"@backstage/plugin-kubernetes": "^0.12.4",
|
||||
"@backstage/plugin-org": "^0.6.36",
|
||||
"@backstage/plugin-permission-react": "^0.4.31",
|
||||
"@backstage/plugin-scaffolder": "^1.28.0",
|
||||
"@backstage/plugin-search": "^1.4.23",
|
||||
"@backstage/plugin-search-react": "^1.8.6",
|
||||
"@backstage/plugin-techdocs": "^1.12.3",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "^1.1.21",
|
||||
"@backstage/plugin-techdocs-react": "^1.2.14",
|
||||
"@backstage/plugin-user-settings": "^0.8.19",
|
||||
"@backstage/theme": "^0.6.4",
|
||||
"@backstage/app-defaults": "^1.4.1",
|
||||
"@backstage/catalog-model": "^1.4.1",
|
||||
"@backstage/cli": "^0.22.9",
|
||||
"@backstage/core-app-api": "^1.9.0",
|
||||
"@backstage/core-components": "^0.13.3",
|
||||
"@backstage/core-plugin-api": "^1.5.3",
|
||||
"@backstage/integration-react": "^1.1.15",
|
||||
"@backstage/plugin-api-docs": "^0.9.6",
|
||||
"@backstage/plugin-catalog": "^1.12.0",
|
||||
"@backstage/plugin-catalog-common": "^1.0.15",
|
||||
"@backstage/plugin-catalog-graph": "^0.2.32",
|
||||
"@backstage/plugin-catalog-import": "^0.9.10",
|
||||
"@backstage/plugin-catalog-react": "^1.8.0",
|
||||
"@backstage/plugin-github-actions": "^0.6.1",
|
||||
"@backstage/plugin-kubernetes": "^0.9.3",
|
||||
"@backstage/plugin-org": "^0.6.10",
|
||||
"@backstage/plugin-permission-react": "^0.4.14",
|
||||
"@backstage/plugin-scaffolder": "^1.14.1",
|
||||
"@backstage/plugin-scaffolder-react": "^1.5.1",
|
||||
"@backstage/plugin-search": "^1.3.3",
|
||||
"@backstage/plugin-search-react": "^1.6.3",
|
||||
"@backstage/plugin-tech-radar": "^0.6.6",
|
||||
"@backstage/plugin-techdocs": "^1.6.5",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "^1.0.15",
|
||||
"@backstage/plugin-techdocs-react": "^1.1.8",
|
||||
"@backstage/plugin-user-settings": "^0.7.5",
|
||||
"@backstage/theme": "^0.4.1",
|
||||
"@cnoe-io/plugin-apache-spark": "0.1.2",
|
||||
"@cnoe-io/plugin-argo-workflows": "0.1.3",
|
||||
"@cnoe-io/plugin-scaffolder-actions-frontend": "0.1.1",
|
||||
"@internal/cnoe-ui-plugin": "^0.1.0",
|
||||
"@internal/plugin-workflows": "^0.1.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@roadiehq/backstage-plugin-argo-cd": "^2.5.1",
|
||||
"@rjsf/core": "^5.8.1",
|
||||
"@rjsf/utils": "^5.8.1",
|
||||
"@roadiehq/backstage-plugin-argo-cd": "^2.3.4",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"fs-extra": "^11.1.1",
|
||||
"history": "^5.0.0",
|
||||
"react": "^18.0.2",
|
||||
"react-dom": "^18.0.2",
|
||||
"react-router": "^6.3.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^1.7.5",
|
||||
"@playwright/test": "^1.32.3",
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@backstage/test-utils": "^1.4.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/react-dom": "*",
|
||||
"cross-env": "^7.0.0"
|
||||
"cross-env": "^7.0.0",
|
||||
"cypress": "^9.7.0",
|
||||
"eslint-plugin-cypress": "^2.10.3",
|
||||
"start-server-and-test": "^1.10.11"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
name="description"
|
||||
content="Backstage is an open platform for building developer portals"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="<%= publicPath %>/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import App from './App';
|
||||
|
||||
describe('App', () => {
|
||||
|
@ -20,10 +20,7 @@ describe('App', () => {
|
|||
] as any,
|
||||
};
|
||||
|
||||
const rendered = render(<App />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
});
|
||||
const rendered = await renderWithEffects(<App />);
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,9 +11,10 @@ import {
|
|||
catalogImportPlugin,
|
||||
} from '@backstage/plugin-catalog-import';
|
||||
import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
|
||||
import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react';
|
||||
import { orgPlugin } from '@backstage/plugin-org';
|
||||
import { SearchPage } from '@backstage/plugin-search';
|
||||
import { TechRadarPage } from '@backstage-community/plugin-tech-radar';
|
||||
import { TechRadarPage } from '@backstage/plugin-tech-radar';
|
||||
import {
|
||||
TechDocsIndexPage,
|
||||
techdocsPlugin,
|
||||
|
@ -22,18 +23,32 @@ import {
|
|||
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
import { UserSettingsPage } from '@backstage/plugin-user-settings';
|
||||
import {apis, keycloakOIDCAuthApiRef} from './apis';
|
||||
import { apis, keycloakOIDCAuthApiRef } from './apis';
|
||||
import { entityPage } from './components/catalog/EntityPage';
|
||||
import { searchPage } from './components/search/SearchPage';
|
||||
import { Root } from './components/Root';
|
||||
|
||||
import {AlertDisplay, OAuthRequestDialog, SignInPage} from '@backstage/core-components';
|
||||
import {
|
||||
AlertDisplay,
|
||||
OAuthRequestDialog,
|
||||
SignInPage,
|
||||
} from '@backstage/core-components';
|
||||
import { createApp } from '@backstage/app-defaults';
|
||||
import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
|
||||
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
|
||||
import {configApiRef, useApi} from "@backstage/core-plugin-api";
|
||||
import { KubernetesClusterPickerExtension } from '@cnoe-io/plugin-scaffolder-actions-frontend';
|
||||
|
||||
import { ThemeProvider } from '@material-ui/core/styles';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import LightIcon from '@material-ui/icons/WbSunny';
|
||||
import {
|
||||
CNOEHomepage,
|
||||
cnoeLightTheme,
|
||||
cnoeDarkTheme,
|
||||
} from '@internal/cnoe-ui-plugin';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
|
@ -46,6 +61,7 @@ const app = createApp({
|
|||
return (
|
||||
<SignInPage
|
||||
{...props}
|
||||
auto
|
||||
provider={{
|
||||
id: 'keycloak-oidc',
|
||||
title: 'Keycloak',
|
||||
|
@ -60,24 +76,47 @@ const app = createApp({
|
|||
bind(catalogPlugin.externalRoutes, {
|
||||
createComponent: scaffolderPlugin.routes.root,
|
||||
viewTechDoc: techdocsPlugin.routes.docRoot,
|
||||
createFromTemplate: scaffolderPlugin.routes.selectedTemplate,
|
||||
});
|
||||
bind(apiDocsPlugin.externalRoutes, {
|
||||
registerApi: catalogImportPlugin.routes.importPage,
|
||||
});
|
||||
bind(scaffolderPlugin.externalRoutes, {
|
||||
registerComponent: catalogImportPlugin.routes.importPage,
|
||||
viewTechDoc: techdocsPlugin.routes.docRoot,
|
||||
});
|
||||
bind(orgPlugin.externalRoutes, {
|
||||
catalogIndex: catalogPlugin.routes.catalogIndex,
|
||||
});
|
||||
}
|
||||
},
|
||||
themes: [
|
||||
{
|
||||
id: 'cnoe-light-theme',
|
||||
title: 'Light Theme',
|
||||
variant: 'light',
|
||||
icon: <LightIcon />,
|
||||
Provider: ({ children }) => (
|
||||
<ThemeProvider theme={cnoeLightTheme}>
|
||||
<CssBaseline>{children}</CssBaseline>
|
||||
</ThemeProvider>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'cnoe-dark-theme',
|
||||
title: 'Dark Theme',
|
||||
variant: 'dark',
|
||||
icon: <LightIcon />,
|
||||
Provider: ({ children }) => (
|
||||
<ThemeProvider theme={cnoeDarkTheme}>
|
||||
<CssBaseline>{children}</CssBaseline>
|
||||
</ThemeProvider>
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Route path="/" element={<Navigate to="home" />} />
|
||||
<Route path="/home" element={<CNOEHomepage />} />
|
||||
<Route path="/catalog" element={<CatalogIndexPage />} />
|
||||
<Route
|
||||
path="/catalog/:namespace/:kind/:name"
|
||||
|
@ -94,7 +133,11 @@ const routes = (
|
|||
<ReportIssue />
|
||||
</TechDocsAddons>
|
||||
</Route>
|
||||
<Route path="/create" element={<ScaffolderPage />} />
|
||||
<Route path="/create" element={<ScaffolderPage />}>
|
||||
<ScaffolderFieldExtensions>
|
||||
<KubernetesClusterPickerExtension />
|
||||
</ScaffolderFieldExtensions>
|
||||
</Route>
|
||||
<Route path="/api-docs" element={<ApiExplorerPage />} />
|
||||
<Route
|
||||
path="/tech-radar"
|
||||
|
|
|
@ -4,16 +4,24 @@ import {
|
|||
ScmAuth,
|
||||
} from '@backstage/integration-react';
|
||||
import {
|
||||
AnyApiFactory, ApiRef, BackstageIdentityApi,
|
||||
AnyApiFactory,
|
||||
ApiRef,
|
||||
BackstageIdentityApi,
|
||||
configApiRef,
|
||||
createApiFactory, createApiRef, discoveryApiRef, oauthRequestApiRef, OpenIdConnectApi, ProfileInfoApi, SessionApi,
|
||||
createApiFactory,
|
||||
createApiRef,
|
||||
discoveryApiRef,
|
||||
oauthRequestApiRef,
|
||||
OpenIdConnectApi,
|
||||
ProfileInfoApi,
|
||||
SessionApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {OAuth2} from "@backstage/core-app-api";
|
||||
import { OAuth2 } from '@backstage/core-app-api';
|
||||
|
||||
export const keycloakOIDCAuthApiRef: ApiRef<
|
||||
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
> = createApiRef({
|
||||
id: 'auth.keycloak-oidc',
|
||||
id: 'auth.keycloak-oidc-provider',
|
||||
});
|
||||
export const apis: AnyApiFactory[] = [
|
||||
createApiFactory({
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import React, { PropsWithChildren } from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import CategoryIcon from '@material-ui/icons/Category';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
import MapIcon from '@material-ui/icons/MyLocation';
|
||||
import LibraryBooks from '@material-ui/icons/LibraryBooks';
|
||||
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
|
||||
import {LogoFull, LogoIcon} from '@internal/cnoe-ui-plugin';
|
||||
|
||||
import {
|
||||
Settings as SidebarSettings,
|
||||
UserSettingsSignInAvatar,
|
||||
|
@ -19,6 +22,7 @@ import {
|
|||
SidebarPage,
|
||||
SidebarScrollWrapper,
|
||||
SidebarSpace,
|
||||
useSidebarOpenState,
|
||||
Link,
|
||||
} from '@backstage/core-components';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
|
@ -41,10 +45,12 @@ const useSidebarLogoStyles = makeStyles({
|
|||
|
||||
const SidebarLogo = () => {
|
||||
const classes = useSidebarLogoStyles();
|
||||
const { isOpen } = useSidebarOpenState();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Link to="/" underline="none" className={classes.link} aria-label="Home">
|
||||
{isOpen ? <LogoFull /> : <LogoIcon />}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
@ -60,7 +66,8 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
|
|||
<SidebarDivider />
|
||||
<SidebarGroup label="Menu" icon={<MenuIcon />}>
|
||||
{/* Global nav, not org-specific */}
|
||||
<SidebarItem icon={HomeIcon} to="catalog" text="Home" />
|
||||
<SidebarItem icon={HomeIcon} to="home" text="Home" />
|
||||
<SidebarItem icon={CategoryIcon} to="catalog" text="Catalog" />
|
||||
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
|
||||
<SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
|
||||
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
|
||||
|
|
|
@ -10,8 +10,11 @@ import {
|
|||
} from '@backstage/plugin-api-docs';
|
||||
import {
|
||||
EntityAboutCard,
|
||||
EntityDependsOnComponentsCard,
|
||||
EntityDependsOnResourcesCard,
|
||||
EntityHasComponentsCard,
|
||||
EntityHasResourcesCard,
|
||||
EntityHasSubcomponentsCard,
|
||||
EntityHasSystemsCard,
|
||||
EntityLayout,
|
||||
EntityLinksCard,
|
||||
|
@ -22,9 +25,11 @@ import {
|
|||
isKind,
|
||||
hasCatalogProcessingErrors,
|
||||
isOrphan,
|
||||
hasRelationWarnings,
|
||||
EntityRelationWarning,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
isGithubActionsAvailable,
|
||||
EntityGithubActionsContent,
|
||||
} from '@backstage/plugin-github-actions';
|
||||
import {
|
||||
EntityUserProfileCard,
|
||||
EntityGroupProfileCard,
|
||||
|
@ -51,11 +56,17 @@ import {
|
|||
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
|
||||
import { EntityKubernetesContent, isKubernetesAvailable } from '@backstage/plugin-kubernetes';
|
||||
import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
|
||||
|
||||
import {
|
||||
EntityArgoCDOverviewCard,
|
||||
isArgocdAvailable
|
||||
EntityArgoWorkflowsOverviewCard,
|
||||
EntityArgoWorkflowsTemplateOverviewCard,
|
||||
isArgoWorkflowsAvailable,
|
||||
} from '@cnoe-io/plugin-argo-workflows';
|
||||
import { ApacheSparkPage } from '@cnoe-io/plugin-apache-spark';
|
||||
import {
|
||||
EntityArgoCDHistoryCard,
|
||||
isArgocdAvailable,
|
||||
} from '@roadiehq/backstage-plugin-argo-cd';
|
||||
|
||||
const techdocsContent = (
|
||||
|
@ -67,7 +78,13 @@ const techdocsContent = (
|
|||
);
|
||||
|
||||
const cicdContent = (
|
||||
// This is an example of how you can implement your company's logic in entity page.
|
||||
// You can for example enforce that all components of type 'service' should use GitHubActions
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isGithubActionsAvailable}>
|
||||
<EntityGithubActionsContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case>
|
||||
<EmptyState
|
||||
title="No CI/CD available for this entity"
|
||||
|
@ -97,14 +114,6 @@ const entityWarningContent = (
|
|||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={hasRelationWarnings}>
|
||||
<Grid item xs={12}>
|
||||
<EntityRelationWarning />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={hasCatalogProcessingErrors}>
|
||||
<Grid item xs={12}>
|
||||
|
@ -122,18 +131,30 @@ const overviewContent = (
|
|||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={e => Boolean(isArgocdAvailable(e))}>
|
||||
<EntitySwitch.Case if={e => isArgoWorkflowsAvailable(e)}>
|
||||
<Grid item md={6}>
|
||||
<EntityArgoCDOverviewCard />
|
||||
<EntityArgoWorkflowsOverviewCard />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityArgoWorkflowsTemplateOverviewCard />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
<EntitySwitch.Case if={e => Boolean(isArgocdAvailable(e))}>
|
||||
<Grid item sm={6}>
|
||||
<EntityArgoCDHistoryCard />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
<Grid item md={6} xs={12}>
|
||||
<EntityCatalogGraphCard variant="gridItem" height={400} />
|
||||
</Grid>
|
||||
|
||||
<Grid item md={4} xs={12}>
|
||||
<EntityLinksCard />
|
||||
</Grid>
|
||||
<Grid item md={8} xs={12}>
|
||||
<EntityHasSubcomponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
|
@ -147,10 +168,6 @@ const serviceEntityPage = (
|
|||
{cicdContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/kubernetes" title="Kubernetes" if={e => isKubernetesAvailable(e)}>
|
||||
<EntityKubernetesContent refreshIntervalMs={30000} />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/api" title="API">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
|
@ -162,6 +179,17 @@ const serviceEntityPage = (
|
|||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/dependencies" title="Dependencies">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
{techdocsContent}
|
||||
</EntityLayout.Route>
|
||||
|
@ -178,12 +206,36 @@ const websiteEntityPage = (
|
|||
{cicdContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/dependencies" title="Dependencies">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
<EntityLayout.Route path="/kubernetes" title="Kubernetes">
|
||||
<EntityKubernetesContent refreshIntervalMs={30000} />
|
||||
</EntityLayout.Route>
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
{techdocsContent}
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
);
|
||||
|
||||
const jobEntityPage = (
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
{overviewContent}
|
||||
</EntityLayout.Route>
|
||||
<EntityLayout.Route path="/apache-spark" title="Apache Spark">
|
||||
<ApacheSparkPage />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
);
|
||||
|
||||
/**
|
||||
* NOTE: This page is designed to work on small screens such as mobile devices.
|
||||
* 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`,
|
||||
|
@ -212,6 +264,9 @@ const componentPage = (
|
|||
<EntitySwitch.Case if={isComponentType('website')}>
|
||||
{websiteEntityPage}
|
||||
</EntitySwitch.Case>
|
||||
<EntitySwitch.Case if={isComponentType('job')}>
|
||||
{jobEntityPage}
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case>{defaultEntityPage}</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
@ -228,6 +283,9 @@ const apiPage = (
|
|||
<Grid item md={6} xs={12}>
|
||||
<EntityCatalogGraphCard variant="gridItem" height={400} />
|
||||
</Grid>
|
||||
<Grid item md={4} xs={12}>
|
||||
<EntityLinksCard />
|
||||
</Grid>
|
||||
<Grid container item md={12}>
|
||||
<Grid item md={6}>
|
||||
<EntityProvidingComponentsCard />
|
||||
|
@ -276,12 +334,9 @@ const groupPage = (
|
|||
<Grid item xs={12} md={6}>
|
||||
<EntityOwnershipCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12}>
|
||||
<EntityMembersListCard />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityLinksCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
|
@ -331,6 +386,9 @@ const systemPage = (
|
|||
unidirectional={false}
|
||||
/>
|
||||
</EntityLayout.Route>
|
||||
<EntityLayout.Route path="/kubernetes" title="Kubernetes">
|
||||
<EntityKubernetesContent refreshIntervalMs={30000} />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import '@backstage/cli/asset-types';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
|
|
@ -9,21 +9,18 @@
|
|||
#
|
||||
# Once the commands have been run, you can build the image using `yarn build-image`
|
||||
|
||||
FROM node:18-bookworm-slim
|
||||
|
||||
# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends python3 g++ build-essential && \
|
||||
yarn config set python /usr/bin/python3
|
||||
FROM node:18-bullseye-slim
|
||||
|
||||
# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
|
||||
# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends libsqlite3-dev
|
||||
apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential wget ca-certificates && \
|
||||
yarn config set python /usr/bin/python3
|
||||
|
||||
RUN wget -P /usr/local/bin/ https://dl.k8s.io/release/v1.27.0/bin/linux/amd64/kubectl \
|
||||
&& chmod +x /usr/local/bin/kubectl
|
||||
|
||||
# From here on we use the least-privileged `node` user to run the backend.
|
||||
USER node
|
||||
|
@ -43,10 +40,11 @@ COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar
|
|||
RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
|
||||
|
||||
RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
|
||||
--mount=type=secret,id=npmrc,target=./.npmrc,uid=1000 \
|
||||
yarn install --frozen-lockfile --production --network-timeout 300000
|
||||
|
||||
# Then copy the rest of the backend bundle, along with any other files we might want.
|
||||
COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./
|
||||
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
|
||||
|
||||
CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]
|
||||
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
|
||||
|
|
|
@ -36,7 +36,7 @@ The backend starts up on port 7007 per default.
|
|||
If you want to use the catalog functionality, you need to add so called
|
||||
locations to the backend. These are places where the backend can find some
|
||||
entity descriptor data to consume and serve. For more information, see
|
||||
[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog).
|
||||
[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog).
|
||||
|
||||
To get started quickly, this template already includes some statically configured example locations
|
||||
in `app-config.yaml` under `catalog.locations`. You can remove and replace these locations as you
|
||||
|
@ -56,4 +56,4 @@ and
|
|||
## Documentation
|
||||
|
||||
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
|
||||
- [Backstage Documentation](https://backstage.io/docs)
|
||||
- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md)
|
||||
|
|
|
@ -16,57 +16,44 @@
|
|||
"build-image": "docker build ../.. -f Dockerfile --tag backstage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.25.0",
|
||||
"@backstage/backend-defaults": "^0.8.1",
|
||||
"@backstage/backend-plugin-api": "^1.2.0",
|
||||
"@backstage/backend-tasks": "^0.6.1",
|
||||
"@backstage/catalog-client": "^1.9.1",
|
||||
"@backstage/catalog-model": "^1.7.3",
|
||||
"@backstage/config": "^1.3.2",
|
||||
"@backstage/errors": "^1.2.7",
|
||||
"@backstage/integration": "^1.16.1",
|
||||
"@backstage/plugin-app-backend": "^0.4.5",
|
||||
"@backstage/plugin-auth-backend": "^0.24.3",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "^0.2.5",
|
||||
"@backstage/plugin-auth-backend-module-oidc-provider": "^0.4.0",
|
||||
"@backstage/plugin-auth-node": "^0.6.0",
|
||||
"@backstage/plugin-catalog-backend": "^1.31.0",
|
||||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.5",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.19.3",
|
||||
"@backstage/plugin-permission-common": "^0.8.4",
|
||||
"@backstage/plugin-permission-node": "^0.8.8",
|
||||
"@backstage/plugin-proxy-backend": "^0.5.11",
|
||||
"@backstage/plugin-scaffolder-backend": "^1.30.0",
|
||||
"@backstage/plugin-scaffolder-backend-module-gitea": "^0.2.6",
|
||||
"@backstage/plugin-scaffolder-backend-module-github": "^0.6.0",
|
||||
"@backstage/plugin-scaffolder-node": "^0.7.0",
|
||||
"@backstage/plugin-search-backend": "^1.8.2",
|
||||
"@backstage/plugin-search-backend-module-catalog": "^0.3.1",
|
||||
"@backstage/plugin-search-backend-module-pg": "^0.5.41",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "^0.3.6",
|
||||
"@backstage/plugin-search-backend-node": "^1.3.8",
|
||||
"@backstage/plugin-techdocs-backend": "^1.11.6",
|
||||
"@backstage/types": "^1.2.1",
|
||||
"@kubernetes/client-node": "~0.20.0",
|
||||
"@roadiehq/backstage-plugin-argo-cd-backend": "3.1.0",
|
||||
"@roadiehq/scaffolder-backend-module-http-request": "^4.3.5",
|
||||
"@roadiehq/scaffolder-backend-module-utils": "3.0.0",
|
||||
"@backstage/backend-common": "^0.19.1",
|
||||
"@backstage/backend-tasks": "^0.5.4",
|
||||
"@backstage/catalog-client": "^1.4.3",
|
||||
"@backstage/catalog-model": "^1.4.1",
|
||||
"@backstage/config": "^1.0.8",
|
||||
"@backstage/integration": "^1.5.1",
|
||||
"@backstage/plugin-app-backend": "^0.3.47",
|
||||
"@backstage/plugin-auth-backend": "^0.18.5",
|
||||
"@backstage/plugin-auth-node": "^0.2.16",
|
||||
"@backstage/plugin-catalog-backend": "^1.11.0",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.11.2",
|
||||
"@backstage/plugin-permission-common": "^0.7.7",
|
||||
"@backstage/plugin-permission-node": "^0.7.10",
|
||||
"@backstage/plugin-proxy-backend": "^0.2.41",
|
||||
"@backstage/plugin-scaffolder-backend": "^1.15.1",
|
||||
"@backstage/plugin-scaffolder-node": "^0.1.5",
|
||||
"@backstage/plugin-search-backend": "^1.3.3",
|
||||
"@backstage/plugin-search-backend-module-pg": "^0.5.8",
|
||||
"@backstage/plugin-search-backend-node": "^1.2.3",
|
||||
"@backstage/plugin-techdocs-backend": "^1.6.4",
|
||||
"@backstage/types": "^1.1.0",
|
||||
"@cnoe-io/plugin-scaffolder-actions": "0.1.1",
|
||||
"@kubernetes/client-node": "^0.18.1",
|
||||
"@roadiehq/scaffolder-backend-module-utils": "^1.8.7",
|
||||
"app": "link:../app",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"better-sqlite3": "^8.0.0",
|
||||
"dockerode": "^3.3.1",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"fs-extra": "~11.2.0",
|
||||
"node-gyp": "^9.0.0",
|
||||
"pg": "^8.11.3",
|
||||
"winston": "^3.2.1"
|
||||
"pg": "^8.3.0",
|
||||
"winston": "^3.2.1",
|
||||
"yaml": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.30.0",
|
||||
"@backstage/cli": "^0.22.9",
|
||||
"@types/dockerode": "^3.3.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express-serve-static-core": "^4.17.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/luxon": "^2.0.4"
|
||||
},
|
||||
"files": [
|
||||
|
|
|
@ -1,45 +1,120 @@
|
|||
import { createBackend } from '@backstage/backend-defaults';
|
||||
import { cnoeScaffolderActions } from './plugins/scaffolder';
|
||||
import { authModuleKeycloakOIDCProvider } from './plugins/auth';
|
||||
/*
|
||||
* Hi!
|
||||
*
|
||||
* Note that this is an EXAMPLE Backstage backend. Please check the README.
|
||||
*
|
||||
* Happy hacking!
|
||||
*/
|
||||
|
||||
const backend = createBackend();
|
||||
import Router from 'express-promise-router';
|
||||
import {
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
getRootLogger,
|
||||
useHotMemoize,
|
||||
notFoundHandler,
|
||||
CacheManager,
|
||||
DatabaseManager,
|
||||
SingleHostDiscovery,
|
||||
UrlReaders,
|
||||
ServerTokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { TaskScheduler } from '@backstage/backend-tasks';
|
||||
import { Config } from '@backstage/config';
|
||||
import app from './plugins/app';
|
||||
import auth from './plugins/auth';
|
||||
import catalog from './plugins/catalog';
|
||||
import scaffolder from './plugins/scaffolder';
|
||||
import proxy from './plugins/proxy';
|
||||
import techdocs from './plugins/techdocs';
|
||||
import search from './plugins/search';
|
||||
import { PluginEnvironment } from './types';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import kubernetes from './plugins/kubernetes';
|
||||
|
||||
// core plugins
|
||||
backend.add(import('@backstage/plugin-app-backend'));
|
||||
backend.add(import('@backstage/plugin-catalog-backend'));
|
||||
backend.add(import('@backstage/plugin-proxy-backend'));
|
||||
backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
|
||||
function makeCreateEnv(config: Config) {
|
||||
const root = getRootLogger();
|
||||
const reader = UrlReaders.default({ logger: root, config });
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
const databaseManager = DatabaseManager.fromConfig(config, { logger: root });
|
||||
const tokenManager = ServerTokenManager.noop();
|
||||
const taskScheduler = TaskScheduler.fromConfig(config);
|
||||
|
||||
// auth plugins
|
||||
backend.add(import('@backstage/plugin-auth-backend'));
|
||||
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
|
||||
const identity = DefaultIdentityClient.create({
|
||||
discovery,
|
||||
});
|
||||
const permissions = ServerPermissionClient.fromConfig(config, {
|
||||
discovery,
|
||||
tokenManager,
|
||||
});
|
||||
|
||||
// scaffolder plugins
|
||||
backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
|
||||
backend.add(
|
||||
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
|
||||
);
|
||||
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
// search plugins
|
||||
backend.add(import('@backstage/plugin-search-backend/alpha'));
|
||||
return (plugin: string): PluginEnvironment => {
|
||||
const logger = root.child({ type: 'plugin', plugin });
|
||||
const database = databaseManager.forPlugin(plugin);
|
||||
const cache = cacheManager.forPlugin(plugin);
|
||||
const scheduler = taskScheduler.forPlugin(plugin);
|
||||
return {
|
||||
logger,
|
||||
database,
|
||||
cache,
|
||||
config,
|
||||
reader,
|
||||
discovery,
|
||||
tokenManager,
|
||||
scheduler,
|
||||
permissions,
|
||||
identity,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
backend.add(import('@backstage/plugin-search-backend-module-catalog'));
|
||||
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
|
||||
async function main() {
|
||||
const config = await loadBackendConfig({
|
||||
argv: process.argv,
|
||||
logger: getRootLogger(),
|
||||
});
|
||||
const createEnv = makeCreateEnv(config);
|
||||
|
||||
// other @backstage plugins
|
||||
backend.add(import('@backstage/plugin-kubernetes-backend'));
|
||||
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
|
||||
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
|
||||
const authEnv = useHotMemoize(module, () => createEnv('auth'));
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
|
||||
const searchEnv = useHotMemoize(module, () => createEnv('search'));
|
||||
const appEnv = useHotMemoize(module, () => createEnv('app'));
|
||||
|
||||
// roadie plugins
|
||||
backend.add(import('@roadiehq/scaffolder-backend-module-utils/new-backend'));
|
||||
backend.add(import('./plugins/argocd_index'));
|
||||
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
|
||||
|
||||
backend.add(
|
||||
import('@roadiehq/scaffolder-backend-module-http-request/new-backend'),
|
||||
);
|
||||
const apiRouter = Router();
|
||||
apiRouter.use('/catalog', await catalog(catalogEnv));
|
||||
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
|
||||
apiRouter.use('/auth', await auth(authEnv));
|
||||
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
|
||||
apiRouter.use('/proxy', await proxy(proxyEnv));
|
||||
apiRouter.use('/search', await search(searchEnv));
|
||||
|
||||
// cnoe plugins
|
||||
backend.add(authModuleKeycloakOIDCProvider);
|
||||
backend.add(cnoeScaffolderActions);
|
||||
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
|
||||
|
||||
backend.start();
|
||||
// Add backends ABOVE this line; this 404 handler is the catch-all fallback
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(config)
|
||||
.addRouter('/api', apiRouter)
|
||||
.addRouter('', await app(appEnv));
|
||||
|
||||
await service.start().catch(err => {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.hot?.accept();
|
||||
main().catch(error => {
|
||||
console.error('Backend failed to start up', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
14
packages/backend/src/plugins/app.ts
Normal file
14
packages/backend/src/plugins/app.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { createRouter } from '@backstage/plugin-app-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
return await createRouter({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
database: env.database,
|
||||
appPackageName: 'app',
|
||||
});
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
import { Config } from '@backstage/config';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { examples } from './gitea-actions';
|
||||
import { Logger } from 'winston';
|
||||
import { ArgoService } from '@roadiehq/backstage-plugin-argo-cd-backend';
|
||||
import { createRouter } from '@roadiehq/backstage-plugin-argo-cd-backend';
|
||||
//import { PluginEnvironment } from '../types';
|
||||
|
||||
|
||||
/*export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config });
|
||||
}*/
|
||||
|
||||
import { loggerToWinstonLogger } from '@backstage/backend-common';
|
||||
|
||||
import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
export const argocdPlugin = createBackendPlugin({
|
||||
pluginId: 'argocd',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
logger: coreServices.logger,
|
||||
config: coreServices.rootConfig,
|
||||
reader: coreServices.urlReader,
|
||||
discovery: coreServices.discovery,
|
||||
auth: coreServices.auth,
|
||||
//tokenManager: coreServices.tokenManager,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
},
|
||||
async init({
|
||||
logger,
|
||||
config,
|
||||
httpRouter,
|
||||
}) {
|
||||
httpRouter.use(
|
||||
await createRouter({
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
config,
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
export function createArgoCDApp(options: { config: Config; logger: Logger }) {
|
||||
const { config, logger } = options;
|
||||
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
projectName?: string;
|
||||
appName: string;
|
||||
argoInstance: string;
|
||||
path: string;
|
||||
labelValue?: string;
|
||||
appNamespace: string;
|
||||
}>({
|
||||
id: 'cnoe:create-argocd-app',
|
||||
description: 'creates argocd app',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: [
|
||||
'repoUrl',
|
||||
'projectName',
|
||||
'appName',
|
||||
'argoInstance',
|
||||
'path',
|
||||
'appNamespace',
|
||||
],
|
||||
properties: {
|
||||
repoUrl: {
|
||||
title: 'Repository Location',
|
||||
type: 'string',
|
||||
},
|
||||
projectName: {
|
||||
title: 'name of the project in argocd',
|
||||
type: 'string',
|
||||
},
|
||||
appName: {
|
||||
title: 'application name in argocd',
|
||||
type: 'string',
|
||||
},
|
||||
appNamespace: {
|
||||
title: 'application name in argocd',
|
||||
type: 'string',
|
||||
},
|
||||
argoInstance: {
|
||||
title: 'backstage argocd instance name defined in app-config.yaml',
|
||||
type: 'string',
|
||||
},
|
||||
path: {
|
||||
title: 'argocd spec path',
|
||||
type: 'string',
|
||||
},
|
||||
labelValue: {
|
||||
title: 'for argocd plugin to locate this app',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const {
|
||||
repoUrl,
|
||||
projectName,
|
||||
appName,
|
||||
argoInstance,
|
||||
path,
|
||||
labelValue,
|
||||
appNamespace,
|
||||
} = ctx.input;
|
||||
|
||||
const argoUserName =
|
||||
config.getOptionalString('argocd.username') ?? 'argocdUsername';
|
||||
const argoPassword =
|
||||
config.getOptionalString('argocd.password') ?? 'argocdPassword';
|
||||
|
||||
const argoSvc = new ArgoService(
|
||||
argoUserName,
|
||||
argoPassword,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
|
||||
const argocdConfig = config
|
||||
.getConfigArray('argocd.appLocatorMethods')
|
||||
.filter(element => element.getString('type') === 'config')
|
||||
.reduce(
|
||||
(acc: Config[], argoApp: Config) =>
|
||||
acc.concat(argoApp.getConfigArray('instances')),
|
||||
[],
|
||||
)
|
||||
.map(instance => ({
|
||||
name: instance.getString('name'),
|
||||
url: instance.getString('url'),
|
||||
token: instance.getOptionalString('token'),
|
||||
username: instance.getOptionalString('username'),
|
||||
password: instance.getOptionalString('password'),
|
||||
}));
|
||||
const matchedArgoInstance = argocdConfig.find(
|
||||
argoHost => argoHost.name === argoInstance,
|
||||
);
|
||||
if (!matchedArgoInstance) {
|
||||
throw new Error(`Unable to find Argo instance named "${argoInstance}"`);
|
||||
}
|
||||
const token =
|
||||
matchedArgoInstance.token ||
|
||||
(await argoSvc.getArgoToken(matchedArgoInstance));
|
||||
|
||||
await argoSvc.createArgoApplication({
|
||||
baseUrl: matchedArgoInstance.url,
|
||||
argoToken: token,
|
||||
appName: appName,
|
||||
projectName: projectName ? projectName : appName,
|
||||
namespace: appNamespace,
|
||||
sourceRepo: repoUrl,
|
||||
sourcePath: path,
|
||||
labelValue: labelValue ? labelValue : appName,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { argocdPlugin as default } from './argocd';
|
|
@ -1,68 +1,57 @@
|
|||
import {
|
||||
createRouter,
|
||||
providers,
|
||||
defaultAuthProviderFactories,
|
||||
} from '@backstage/plugin-auth-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import {
|
||||
DEFAULT_NAMESPACE,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { JsonArray } from '@backstage/types';
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
authProvidersExtensionPoint,
|
||||
createOAuthProviderFactory,
|
||||
OAuthAuthenticatorResult,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
oidcAuthenticator,
|
||||
OidcAuthResult,
|
||||
} from '@backstage/plugin-auth-backend-module-oidc-provider';
|
||||
|
||||
export const authModuleKeycloakOIDCProvider = createBackendModule({
|
||||
pluginId: 'auth',
|
||||
moduleId: 'keycloak-oidc',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
providers: authProvidersExtensionPoint,
|
||||
},
|
||||
async init({ providers }) {
|
||||
providers.registerProvider({
|
||||
providerId: 'keycloak-oidc',
|
||||
factory: createOAuthProviderFactory({
|
||||
authenticator: oidcAuthenticator,
|
||||
profileTransform: async (
|
||||
input: OAuthAuthenticatorResult<OidcAuthResult>,
|
||||
) => ({
|
||||
profile: {
|
||||
email: input.fullProfile.userinfo.email,
|
||||
picture: input.fullProfile.userinfo.picture,
|
||||
displayName: input.fullProfile.userinfo.name,
|
||||
},
|
||||
}),
|
||||
async signInResolver(info, ctx) {
|
||||
const { profile } = info;
|
||||
if (!profile.displayName) {
|
||||
throw new Error(
|
||||
'Login failed, user profile does not contain a valid name',
|
||||
);
|
||||
}
|
||||
// should use users from catalog
|
||||
const userRef = stringifyEntityRef({
|
||||
kind: 'User',
|
||||
name: info.profile.displayName!,
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
});
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const opts = {
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
database: env.database,
|
||||
discovery: env.discovery,
|
||||
tokenManager: env.tokenManager,
|
||||
providerFactories: {
|
||||
...defaultAuthProviderFactories,
|
||||
},
|
||||
};
|
||||
|
||||
return ctx.issueToken({
|
||||
claims: {
|
||||
sub: userRef,
|
||||
ent: [userRef],
|
||||
groups:
|
||||
(info.result.fullProfile.userinfo.groups as JsonArray) ||
|
||||
[],
|
||||
},
|
||||
});
|
||||
const envName = env.config
|
||||
.getOptionalConfig('auth')
|
||||
?.getOptionalString('auth');
|
||||
if (envName === 'local') {
|
||||
return await createRouter(opts);
|
||||
}
|
||||
|
||||
const keycloakAuth = (opts.providerFactories['keycloak-oidc'] =
|
||||
providers.oidc.create({
|
||||
signIn: {
|
||||
resolver(info, ctx) {
|
||||
const userRef = stringifyEntityRef({
|
||||
kind: 'User',
|
||||
name: info.result.userinfo.sub,
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
});
|
||||
return ctx.issueToken({
|
||||
claims: {
|
||||
sub: userRef,
|
||||
ent: [userRef],
|
||||
groups: (info.result.userinfo.groups as JsonArray) || [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}));
|
||||
opts.providerFactories['keycloak-oidc'] = keycloakAuth;
|
||||
|
||||
return await createRouter(opts);
|
||||
}
|
||||
|
|
14
packages/backend/src/plugins/catalog.ts
Normal file
14
packages/backend/src/plugins/catalog.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
|
||||
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
builder.addProcessor(new ScaffolderEntitiesProcessor());
|
||||
const { processingEngine, router } = await builder.build();
|
||||
await processingEngine.start();
|
||||
return router;
|
||||
}
|
|
@ -1,43 +1,11 @@
|
|||
import {
|
||||
createTemplateAction,
|
||||
executeShellCommand,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { dumpYaml } from '@kubernetes/client-node';
|
||||
import yaml from 'js-yaml';
|
||||
import YAML from 'yaml';
|
||||
import { Config } from '@backstage/config';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-common';
|
||||
import fs from 'fs-extra';
|
||||
import { executeShellCommand } from '@backstage/plugin-scaffolder-backend';
|
||||
|
||||
interface Cluster {
|
||||
name: string;
|
||||
cluster: {
|
||||
server: string;
|
||||
'insecure-skip-tls-verify': boolean;
|
||||
'certificate-authority-data'?: string;
|
||||
'certificate-authority'?: string;
|
||||
};
|
||||
}
|
||||
interface Context {
|
||||
name: string;
|
||||
context: {
|
||||
cluster: string;
|
||||
user: string;
|
||||
};
|
||||
}
|
||||
interface User {
|
||||
name: string;
|
||||
user: {
|
||||
token?: string;
|
||||
};
|
||||
}
|
||||
interface ConfFile {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
'current-context': string;
|
||||
contexts: Context[];
|
||||
clusters: Cluster[];
|
||||
users: User[];
|
||||
}
|
||||
export const createKubernetesApply = (config: Config) => {
|
||||
return createTemplateAction<{
|
||||
manifestString?: string;
|
||||
|
@ -89,34 +57,24 @@ export const createKubernetesApply = (config: Config) => {
|
|||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
let manifestPath = resolveSafeChildPath(
|
||||
ctx.workspacePath,
|
||||
'to-be-applied.yaml',
|
||||
);
|
||||
let obj: any;
|
||||
if (ctx.input.manifestString) {
|
||||
fs.writeFileSync(manifestPath, ctx.input.manifestString, {
|
||||
encoding: 'utf8',
|
||||
mode: '600',
|
||||
});
|
||||
obj = YAML.parse(ctx.input.manifestString);
|
||||
} else if (ctx.input.manifestObject) {
|
||||
fs.writeFileSync(manifestPath, yaml.dump(ctx.input.manifestObject), {
|
||||
encoding: 'utf8',
|
||||
mode: '600',
|
||||
});
|
||||
obj = ctx.input.manifestObject;
|
||||
} else {
|
||||
const filePath = resolveSafeChildPath(
|
||||
ctx.workspacePath,
|
||||
ctx.input.manifestPath!,
|
||||
);
|
||||
manifestPath = filePath;
|
||||
const fileContent = fs.readFileSync(filePath, 'utf8');
|
||||
obj = YAML.parse(fileContent);
|
||||
}
|
||||
const fileContent = fs.readFileSync(manifestPath, 'utf8');
|
||||
const objList: any[] = yaml.loadAll(fileContent);
|
||||
|
||||
if (ctx.input.clusterName) {
|
||||
// Supports SA token authentication only
|
||||
const targetCluster = getClusterConfig(ctx.input.clusterName!, config);
|
||||
const confFile: ConfFile = {
|
||||
const confFile = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Config',
|
||||
'current-context': ctx.input.clusterName,
|
||||
|
@ -133,9 +91,8 @@ export const createKubernetesApply = (config: Config) => {
|
|||
{
|
||||
name: ctx.input.clusterName,
|
||||
cluster: {
|
||||
'certificate-authority-data': targetCluster.getString('caData'),
|
||||
server: targetCluster.getString('url'),
|
||||
'insecure-skip-tls-verify':
|
||||
!!targetCluster.getOptionalBoolean('skipTLSVerify'),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -148,84 +105,43 @@ export const createKubernetesApply = (config: Config) => {
|
|||
},
|
||||
],
|
||||
};
|
||||
if (!confFile.clusters[0].cluster['insecure-skip-tls-verify']) {
|
||||
let caDataRaw = targetCluster.getOptionalString('caData');
|
||||
if (caDataRaw?.startsWith('-----BEGIN CERTIFICATE-----')) {
|
||||
caDataRaw = Buffer.from(
|
||||
targetCluster.getString('caData'),
|
||||
'utf8',
|
||||
).toString('base64');
|
||||
}
|
||||
confFile.clusters[0].cluster['certificate-authority-data'] =
|
||||
caDataRaw;
|
||||
if (
|
||||
targetCluster.getOptionalString('caFile') &&
|
||||
!(
|
||||
targetCluster.getOptionalString('caFile')?.length === 0 ||
|
||||
targetCluster.getOptionalString('caFile') === null
|
||||
)
|
||||
) {
|
||||
confFile.clusters[0].cluster['certificate-authority'] =
|
||||
targetCluster.getString('caFile');
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
targetCluster
|
||||
.getString('caData')
|
||||
.startsWith('-----BEGIN CERTIFICATE-----')
|
||||
) {
|
||||
confFile.clusters[0].cluster['certificate-authority-data'] =
|
||||
Buffer.from(targetCluster.getString('caData'), 'utf8').toString(
|
||||
'base64',
|
||||
);
|
||||
}
|
||||
const confString = dumpYaml(confFile);
|
||||
const confFilePath = resolveSafeChildPath(ctx.workspacePath, 'config');
|
||||
fs.writeFileSync(confFilePath, confString, {
|
||||
encoding: 'utf8',
|
||||
mode: '600',
|
||||
});
|
||||
await executeShellCommand({
|
||||
command: 'cat',
|
||||
args: [confFilePath],
|
||||
logStream: ctx.logStream,
|
||||
});
|
||||
await executeShellCommand({
|
||||
command: 'cat',
|
||||
args: [manifestPath],
|
||||
logStream: ctx.logStream,
|
||||
});
|
||||
let counter = 1;
|
||||
for (const obj of objList) {
|
||||
let manifestFilePath = resolveSafeChildPath(
|
||||
ctx.workspacePath,
|
||||
'to-be-applied-' + counter.toString() + '.yaml',
|
||||
);
|
||||
fs.writeFileSync(manifestFilePath, yaml.dump(obj), {
|
||||
encoding: 'utf8',
|
||||
mode: '600',
|
||||
const manifestPath = resolveSafeChildPath(
|
||||
ctx.workspacePath,
|
||||
ctx.input.manifestPath!,
|
||||
);
|
||||
if (obj.metadata.generateName !== undefined) {
|
||||
await executeShellCommand({
|
||||
command: 'kubectl',
|
||||
args: ['--kubeconfig', confFilePath, 'create', '-f', manifestPath],
|
||||
logStream: ctx.logStream,
|
||||
});
|
||||
if (obj.metadata.generateName !== undefined) {
|
||||
await executeShellCommand({
|
||||
command: 'kubectl',
|
||||
args: [
|
||||
'--kubeconfig',
|
||||
confFilePath,
|
||||
'create',
|
||||
'-f',
|
||||
manifestFilePath,
|
||||
],
|
||||
logStream: ctx.logStream,
|
||||
});
|
||||
} else {
|
||||
await executeShellCommand({
|
||||
command: 'kubectl',
|
||||
args: [
|
||||
'--kubeconfig',
|
||||
confFilePath,
|
||||
'apply',
|
||||
'-f',
|
||||
manifestFilePath,
|
||||
],
|
||||
logStream: ctx.logStream,
|
||||
});
|
||||
}
|
||||
counter += 1;
|
||||
return;
|
||||
}
|
||||
await executeShellCommand({
|
||||
command: 'kubectl',
|
||||
args: ['--kubeconfig', confFilePath, 'apply', '-f', manifestPath],
|
||||
logStream: ctx.logStream,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw new Error('please specify a valid cluster name');
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -252,4 +168,4 @@ function getClusterConfig(name: string, config: Config): Config {
|
|||
throw new Error(`Cluster with name ${name} not found`);
|
||||
}
|
||||
return clusters[0];
|
||||
}
|
||||
}
|
|
@ -1,600 +0,0 @@
|
|||
import { InputError } from '@backstage/errors';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
getGiteaRequestOptions,
|
||||
GiteaIntegrationConfig,
|
||||
ScmIntegrationRegistry,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
createTemplateAction,
|
||||
getRepoSourceDirectory,
|
||||
initRepoAndPush,
|
||||
TemplateExample,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import crypto from 'crypto';
|
||||
import yaml from 'yaml';
|
||||
|
||||
export const examples: TemplateExample[] = [
|
||||
{
|
||||
description:
|
||||
'Initializes a Gitea repository using the content of the workspace and publish it to Gitea with default configuration.',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitea',
|
||||
name: 'Publish to Gitea',
|
||||
input: {
|
||||
repoUrl: 'gitea.com?repo=repo&owner=owner',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Initializes a Gitea repository with a description.',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitea',
|
||||
name: 'Publish to Gitea',
|
||||
input: {
|
||||
repoUrl: 'gitea.com?repo=repo&owner=owner',
|
||||
description: 'Initialize a gitea repository',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Initializes a Gitea repository with a default Branch, if not set defaults to main',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitea',
|
||||
name: 'Publish to Gitea',
|
||||
input: {
|
||||
repoUrl: 'gitea.com?repo=repo&owner=owner',
|
||||
defaultBranch: 'main',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Initializes a Gitea repository with an initial commit message, if not set defaults to initial commit',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitea',
|
||||
name: 'Publish to Gitea',
|
||||
input: {
|
||||
repoUrl: 'gitea.com?repo=repo&owner=owner',
|
||||
gitCommitMessage: 'Initial Commit Message',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Initializes a Gitea repository with a repo Author Name, if not set defaults to Scaffolder',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitea',
|
||||
name: 'Publish to Gitea',
|
||||
input: {
|
||||
repoUrl: 'gitea.com?repo=repo&owner=owner',
|
||||
gitAuthorName: 'John Doe',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Initializes a Gitea repository with a repo Author Email',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitea',
|
||||
name: 'Publish to Gitea',
|
||||
input: {
|
||||
repoUrl: 'gitea.com?repo=repo&owner=owner',
|
||||
gitAuthorEmail: 'johndoe@email.com',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitea',
|
||||
name: 'Publish to Gitea',
|
||||
input: {
|
||||
repoUrl: 'gitea.com?repo=repo&owner=owner',
|
||||
sourcePath: 'repository/',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Initializes a Gitea repository with all properties being set',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitea',
|
||||
name: 'Publish to Gitea',
|
||||
input: {
|
||||
repoUrl: 'gitea.com?repo=repo&owner=owner',
|
||||
description: 'Initialize a gitea repository',
|
||||
defaultBranch: 'staging',
|
||||
gitCommitMessage: 'Initial Commit Message',
|
||||
gitAuthorName: 'John Doe',
|
||||
gitAuthorEmail: 'johndoe@email.com',
|
||||
sourcePath: 'repository/',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const parseRepoUrl = (
|
||||
repoUrl: string,
|
||||
integrations: ScmIntegrationRegistry,
|
||||
): {
|
||||
repo: string;
|
||||
host: string;
|
||||
owner?: string;
|
||||
organization?: string;
|
||||
workspace?: string;
|
||||
project?: string;
|
||||
} => {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(`https://${repoUrl}`);
|
||||
} catch (error) {
|
||||
throw new InputError(
|
||||
`Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`,
|
||||
);
|
||||
}
|
||||
const host = parsed.host;
|
||||
const owner = parsed.searchParams.get('owner') ?? undefined;
|
||||
const organization = parsed.searchParams.get('organization') ?? undefined;
|
||||
const workspace = parsed.searchParams.get('workspace') ?? undefined;
|
||||
const project = parsed.searchParams.get('project') ?? undefined;
|
||||
|
||||
const type = integrations.byHost(host)?.type;
|
||||
|
||||
if (!type) {
|
||||
throw new InputError(
|
||||
`No matching integration configuration for host ${host}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
|
||||
const repo: string = parsed.searchParams.get('repo')!;
|
||||
switch (type) {
|
||||
case 'bitbucket': {
|
||||
if (host === 'www.bitbucket.org') {
|
||||
checkRequiredParams(parsed, 'workspace');
|
||||
}
|
||||
checkRequiredParams(parsed, 'project', 'repo');
|
||||
break;
|
||||
}
|
||||
case 'gitlab': {
|
||||
// project is the projectID, and if defined, owner and repo won't be needed.
|
||||
if (!project) {
|
||||
checkRequiredParams(parsed, 'owner', 'repo');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'gitea': {
|
||||
checkRequiredParams(parsed, 'repo');
|
||||
break;
|
||||
}
|
||||
case 'gerrit': {
|
||||
checkRequiredParams(parsed, 'repo');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
checkRequiredParams(parsed, 'repo', 'owner');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { host, owner, repo, organization, workspace, project };
|
||||
};
|
||||
|
||||
function checkRequiredParams(repoUrl: URL, ...params: string[]) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
if (!repoUrl.searchParams.get(params[i])) {
|
||||
throw new InputError(
|
||||
`Invalid repo URL passed to publisher: ${repoUrl.toString()}, missing ${
|
||||
params[i]
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// const checkGiteaContentUrl = async (
|
||||
// config: GiteaIntegrationConfig,
|
||||
// options: {
|
||||
// owner?: string;
|
||||
// repo: string;
|
||||
// defaultBranch?: string;
|
||||
// },
|
||||
// ): Promise<Response> => {
|
||||
// const { owner, repo, defaultBranch } = options;
|
||||
// let response: Response;
|
||||
// const getOptions: RequestInit = {
|
||||
// method: 'GET',
|
||||
// };
|
||||
//
|
||||
// try {
|
||||
// response = await fetch(
|
||||
// `${config.baseUrl}/${owner}/${repo}/src/branch/${defaultBranch}`,
|
||||
// getOptions,
|
||||
// );
|
||||
// } catch (e) {
|
||||
// throw new Error(
|
||||
// `Unable to get the repository: ${owner}/${repo} metadata , ${e}`,
|
||||
// );
|
||||
// }
|
||||
// return response;
|
||||
// };
|
||||
|
||||
const checkGiteaOrg = async (
|
||||
config: GiteaIntegrationConfig,
|
||||
options: {
|
||||
owner: string;
|
||||
},
|
||||
): Promise<void> => {
|
||||
const { owner } = options;
|
||||
let response: Response;
|
||||
// check first if the org = owner exists
|
||||
const getOptions: RequestInit = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...getGiteaRequestOptions(config).headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
try {
|
||||
response = await fetch(
|
||||
`${config.baseUrl}/api/v1/orgs/${owner}`,
|
||||
getOptions,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to get the Organization: ${owner}, ${e}`);
|
||||
}
|
||||
if (response.status !== 200) {
|
||||
throw new Error(
|
||||
`Organization ${owner} do not exist. Please create it first !`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const createGiteaProject = async (
|
||||
config: GiteaIntegrationConfig,
|
||||
options: {
|
||||
projectName: string;
|
||||
owner?: string;
|
||||
description: string;
|
||||
},
|
||||
): Promise<void> => {
|
||||
const { projectName, description, owner } = options;
|
||||
|
||||
/*
|
||||
Several options exist to create a repository using either the user or organisation
|
||||
User: https://gitea.com/api/swagger#/user/createCurrentUserRepo
|
||||
Api: URL/api/v1/user/repos
|
||||
Remark: The user is the username defined part of the backstage integration config for the gitea URL !
|
||||
|
||||
Org: https://gitea.com/api/swagger#/organization/createOrgRepo
|
||||
Api: URL/api/v1/orgs/${org_owner}/repos
|
||||
This is the default scenario that we support currently
|
||||
*/
|
||||
let response: Response;
|
||||
|
||||
const postOptions: RequestInit = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: projectName,
|
||||
description,
|
||||
}),
|
||||
headers: {
|
||||
...getGiteaRequestOptions(config).headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
if (owner) {
|
||||
try {
|
||||
response = await fetch(
|
||||
`${config.baseUrl}/api/v1/orgs/${owner}/repos`,
|
||||
postOptions,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to create repository, ${e}`);
|
||||
}
|
||||
if (response.status !== 201) {
|
||||
throw new Error(
|
||||
`Unable to create repository, ${response.status} ${
|
||||
response.statusText
|
||||
}, ${await response.text()}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
response = await fetch(
|
||||
`${config.baseUrl}/api/v1/user/repos`,
|
||||
postOptions,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to create repository, ${e}`);
|
||||
}
|
||||
if (response.status !== 201) {
|
||||
throw new Error(
|
||||
`Unable to create repository, ${response.status} ${
|
||||
response.statusText
|
||||
}, ${await response.text()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const generateCommitMessage = (
|
||||
config: Config,
|
||||
commitSubject?: string,
|
||||
): string => {
|
||||
const changeId = crypto.randomBytes(20).toString('hex');
|
||||
const msg = `${
|
||||
config.getOptionalString('scaffolder.defaultCommitMessage') || commitSubject
|
||||
}\n\nChange-Id: I${changeId}`;
|
||||
return msg;
|
||||
};
|
||||
|
||||
// async function checkDurationLimit(fn: () => void, timeLimit: number): Promise<boolean> {
|
||||
//
|
||||
// const startTime = process.hrtime();
|
||||
//
|
||||
// // Call the function
|
||||
// await fn();
|
||||
//
|
||||
// const endTime = process.hrtime(startTime);
|
||||
// const durationInMs = endTime[0] * 1000 + endTime[1] / 1e6;
|
||||
//
|
||||
// // Check if the duration exceeds the time limit
|
||||
// return durationInMs <= timeLimit;
|
||||
// }
|
||||
//
|
||||
// async function checkAvailabilityGiteaRepository(
|
||||
// integrationConfig: GiteaIntegrationConfig,
|
||||
// options: {
|
||||
// owner?: string;
|
||||
// repo: string;
|
||||
// defaultBranch: string;
|
||||
// ctx: ActionContext<any>;
|
||||
// },
|
||||
// ) {
|
||||
// const { owner, repo, defaultBranch, ctx } = options;
|
||||
// const sleep = (ms: number | undefined) => new Promise(r => setTimeout(r, ms));
|
||||
// let response: Response;
|
||||
//
|
||||
// const p = new Promise<void>((resolve, reject) => {
|
||||
// setTimeout(async () => {
|
||||
// response = await checkGiteaContentUrl(integrationConfig, {
|
||||
// owner,
|
||||
// repo,
|
||||
// defaultBranch,
|
||||
// });
|
||||
//
|
||||
// while (response.status !== 200) {
|
||||
// if (ctx.signal?.aborted) return;
|
||||
// await sleep(1000);
|
||||
// response = await checkGiteaContentUrl(integrationConfig, {
|
||||
// owner,
|
||||
// repo,
|
||||
// defaultBranch,
|
||||
// });
|
||||
// }
|
||||
// resolve()
|
||||
// },
|
||||
// 5000
|
||||
// )
|
||||
// })
|
||||
// return p
|
||||
//
|
||||
// }
|
||||
|
||||
/**
|
||||
* Creates a new action that initializes a git repository using the content of the workspace.
|
||||
* and publishes it to a Gitea instance.
|
||||
* @public
|
||||
*/
|
||||
export function createPublishGiteaAction(options: {
|
||||
integrations: ScmIntegrations;
|
||||
config: Config;
|
||||
}) {
|
||||
const { integrations, config } = options;
|
||||
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
description: string;
|
||||
defaultBranch?: string;
|
||||
gitCommitMessage?: string;
|
||||
gitAuthorName?: string;
|
||||
gitAuthorEmail?: string;
|
||||
sourcePath?: string;
|
||||
}>({
|
||||
id: 'publish:gitea',
|
||||
description:
|
||||
'Initializes a git repository using the content of the workspace, and publishes it to Gitea.',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['repoUrl'],
|
||||
properties: {
|
||||
repoUrl: {
|
||||
title: 'Repository Location',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
title: 'Repository Description',
|
||||
type: 'string',
|
||||
},
|
||||
defaultBranch: {
|
||||
title: 'Default Branch',
|
||||
type: 'string',
|
||||
description: `Sets the default branch on the repository. The default value is 'main'`,
|
||||
},
|
||||
gitCommitMessage: {
|
||||
title: 'Git Commit Message',
|
||||
type: 'string',
|
||||
description: `Sets the commit message on the repository. The default value is 'initial commit'`,
|
||||
},
|
||||
gitAuthorName: {
|
||||
title: 'Default Author Name',
|
||||
type: 'string',
|
||||
description: `Sets the default author name for the commit. The default value is 'Scaffolder'`,
|
||||
},
|
||||
gitAuthorEmail: {
|
||||
title: 'Default Author Email',
|
||||
type: 'string',
|
||||
description: `Sets the default author email for the commit.`,
|
||||
},
|
||||
sourcePath: {
|
||||
title: 'Source Path',
|
||||
type: 'string',
|
||||
description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
remoteUrl: {
|
||||
title: 'A URL to the repository with the provider',
|
||||
type: 'string',
|
||||
},
|
||||
repoContentsUrl: {
|
||||
title: 'A URL to the root of the repository',
|
||||
type: 'string',
|
||||
},
|
||||
commitHash: {
|
||||
title: 'The git commit hash of the initial commit',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const {
|
||||
repoUrl,
|
||||
description,
|
||||
defaultBranch = 'main',
|
||||
gitAuthorName,
|
||||
gitAuthorEmail,
|
||||
gitCommitMessage = 'initial commit',
|
||||
sourcePath,
|
||||
} = ctx.input;
|
||||
const { repo, host, owner } = parseRepoUrl(repoUrl, integrations);
|
||||
const integrationConfig = integrations.gitea.byHost(host);
|
||||
if (!integrationConfig) {
|
||||
throw new InputError(
|
||||
`No matching integration configuration for host ${host}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
const { username, password } = integrationConfig.config;
|
||||
|
||||
if (!username || !password) {
|
||||
throw new Error('Credentials for the gitea ${host} required.');
|
||||
}
|
||||
|
||||
// check if the org exists within the gitea server
|
||||
if (owner && owner !== username) {
|
||||
await checkGiteaOrg(integrationConfig.config, { owner });
|
||||
}
|
||||
|
||||
await createGiteaProject(integrationConfig.config, {
|
||||
description,
|
||||
owner: owner,
|
||||
projectName: repo,
|
||||
});
|
||||
|
||||
const auth = {
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
const gitAuthorInfo = {
|
||||
name: gitAuthorName
|
||||
? gitAuthorName
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.name'),
|
||||
email: gitAuthorEmail
|
||||
? gitAuthorEmail
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.email'),
|
||||
};
|
||||
// The owner to be used should be either the org name or user authenticated with the gitea server
|
||||
const repoOwner = owner ? owner : username;
|
||||
const remoteUrl = `${integrationConfig.config.baseUrl}/${repoOwner}/${repo}.git`;
|
||||
const commitResult = await initRepoAndPush({
|
||||
dir: getRepoSourceDirectory(ctx.workspacePath, sourcePath),
|
||||
remoteUrl,
|
||||
auth,
|
||||
defaultBranch,
|
||||
logger: ctx.logger,
|
||||
commitMessage: generateCommitMessage(config, gitCommitMessage),
|
||||
gitAuthorInfo,
|
||||
});
|
||||
|
||||
// Check if the gitea repo URL is available before to exit
|
||||
const operationTimeLimit = 5000; // 20 seconds
|
||||
const sleep = (ms: number | undefined) =>
|
||||
new Promise(r => setTimeout(r, ms));
|
||||
await sleep(operationTimeLimit);
|
||||
// await checkAvailabilityGiteaRepository(
|
||||
// integrationConfig.config, {
|
||||
// repoOwner,
|
||||
// repo,
|
||||
// defaultBranch,
|
||||
// ctx,
|
||||
// }
|
||||
// )
|
||||
// const checkDuration = await checkDurationLimit(
|
||||
// async () =>
|
||||
// await checkAvailabilityGiteaRepository(integrationConfig.config, {
|
||||
// repoOwner,
|
||||
// repo,
|
||||
// defaultBranch,
|
||||
// ctx,
|
||||
// }),
|
||||
// operationTimeLimit,
|
||||
// );
|
||||
//
|
||||
// if (!checkDuration) {
|
||||
// console.log('Operation exceeded the time limit.');
|
||||
// }
|
||||
|
||||
const repoContentsUrl = `${integrationConfig.config.baseUrl}/${repoOwner}/${repo}/src/branch/${defaultBranch}/`;
|
||||
ctx.output('remoteUrl', remoteUrl);
|
||||
ctx.output('commitHash', commitResult?.commitHash);
|
||||
ctx.output('repoContentsUrl', repoContentsUrl);
|
||||
},
|
||||
});
|
||||
}
|
18
packages/backend/src/plugins/kubernetes.ts
Normal file
18
packages/backend/src/plugins/kubernetes.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const catalogApi = new CatalogClient({ discoveryApi: env.discovery });
|
||||
const { router } = await KubernetesBuilder.createBuilder({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
catalogApi,
|
||||
permissions: env.permissions,
|
||||
}).build();
|
||||
|
||||
return router;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
// Add type annotations to fix TS2742
|
||||
type SanitizeResourceInput = {
|
||||
document: string;
|
||||
};
|
||||
|
||||
type SanitizeResourceOutput = {
|
||||
sanitized: string;
|
||||
};
|
||||
|
||||
export const createSanitizeResource = () => {
|
||||
return createTemplateAction<SanitizeResourceInput, SanitizeResourceOutput>({
|
||||
id: 'cnoe:utils:sanitize',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['document'],
|
||||
properties: {
|
||||
document: {
|
||||
type: 'string',
|
||||
title: 'Document',
|
||||
description: 'The document to be sanitized',
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sanitized: {
|
||||
type: 'string',
|
||||
description: 'The sanitized yaml string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const obj = yaml.load(ctx.input.document);
|
||||
ctx.output('sanitized', yaml.dump(removeEmptyObjects(obj)));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Remove empty elements from an object
|
||||
function removeEmptyObjects(obj: any): any {
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const newObj: any = Array.isArray(obj) ? [] : {};
|
||||
|
||||
for (const key in obj) {
|
||||
const value = obj[key];
|
||||
const newValue = removeEmptyObjects(value);
|
||||
if (
|
||||
!(
|
||||
newValue === null ||
|
||||
newValue === undefined ||
|
||||
(typeof newValue === 'object' && Object.keys(newValue).length === 0)
|
||||
)
|
||||
) {
|
||||
newObj[key] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
return newObj;
|
||||
}
|
|
@ -1,44 +1,82 @@
|
|||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { createPublishGiteaAction } from './gitea-actions';
|
||||
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
import {
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
import { createArgoCDApp } from './argocd';
|
||||
import { getRootLogger } from '@backstage/backend-common';
|
||||
import { createKubernetesApply } from './k8s-apply';
|
||||
import { createSanitizeResource } from './sanitize';
|
||||
import { createVerifyDependency } from './verify';
|
||||
createBuiltinActions,
|
||||
createRouter,
|
||||
} from '@backstage/plugin-scaffolder-backend';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import {
|
||||
createZipAction,
|
||||
createSleepAction,
|
||||
createWriteFileAction,
|
||||
createAppendFileAction,
|
||||
createMergeJSONAction,
|
||||
createMergeAction,
|
||||
createParseFileAction,
|
||||
createSerializeYamlAction,
|
||||
createSerializeJsonAction,
|
||||
createJSONataAction,
|
||||
createYamlJSONataTransformAction,
|
||||
createJsonJSONataTransformAction,
|
||||
} from '@roadiehq/scaffolder-backend-module-utils';
|
||||
import {
|
||||
// createKubernetesApply,
|
||||
createSanitizeResource,
|
||||
createVerifyDependency,
|
||||
} from '@cnoe-io/plugin-scaffolder-actions';
|
||||
|
||||
export const cnoeScaffolderActions = createBackendModule({
|
||||
pluginId: 'scaffolder',
|
||||
moduleId: 'cnoe-actions',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
scaffolder: scaffolderActionsExtensionPoint,
|
||||
config: coreServices.rootConfig,
|
||||
},
|
||||
async init({ scaffolder, config }) {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const logger = getRootLogger();
|
||||
import { createKubernetesApply } from './cnoe-kube';
|
||||
|
||||
scaffolder.addActions(
|
||||
createPublishGiteaAction({
|
||||
integrations,
|
||||
config,
|
||||
}),
|
||||
createArgoCDApp({
|
||||
config,
|
||||
logger,
|
||||
}),
|
||||
createKubernetesApply(config),
|
||||
createSanitizeResource(),
|
||||
createVerifyDependency(),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const catalogClient = new CatalogClient({
|
||||
discoveryApi: env.discovery,
|
||||
});
|
||||
const integrations = ScmIntegrations.fromConfig(env.config);
|
||||
|
||||
const builtInActions = createBuiltinActions({
|
||||
integrations,
|
||||
catalogClient,
|
||||
config: env.config,
|
||||
reader: env.reader,
|
||||
});
|
||||
|
||||
const scaffolderBackendModuleUtils = [
|
||||
createZipAction(),
|
||||
createSleepAction(),
|
||||
createWriteFileAction(),
|
||||
createAppendFileAction(),
|
||||
createMergeJSONAction({}),
|
||||
createMergeAction(),
|
||||
createParseFileAction(),
|
||||
createSerializeYamlAction(),
|
||||
createSerializeJsonAction(),
|
||||
createJSONataAction(),
|
||||
createYamlJSONataTransformAction(),
|
||||
createJsonJSONataTransformAction(),
|
||||
];
|
||||
|
||||
const cnoeActions = [
|
||||
createSanitizeResource(),
|
||||
createVerifyDependency(),
|
||||
createKubernetesApply(env.config),
|
||||
];
|
||||
|
||||
const actions = [
|
||||
...builtInActions,
|
||||
...scaffolderBackendModuleUtils,
|
||||
...cnoeActions,
|
||||
];
|
||||
|
||||
return await createRouter({
|
||||
actions: actions,
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
database: env.database,
|
||||
reader: env.reader,
|
||||
catalogClient: catalogClient,
|
||||
identity: env.identity,
|
||||
});
|
||||
}
|
||||
|
|
66
packages/backend/src/plugins/search.ts
Normal file
66
packages/backend/src/plugins/search.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { useHotCleanup } from '@backstage/backend-common';
|
||||
import { createRouter } from '@backstage/plugin-search-backend';
|
||||
import {
|
||||
IndexBuilder,
|
||||
LunrSearchEngine,
|
||||
} from '@backstage/plugin-search-backend-node';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend';
|
||||
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend';
|
||||
import { Router } from 'express';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
// Initialize a connection to a search engine.
|
||||
const searchEngine = new LunrSearchEngine({
|
||||
logger: env.logger,
|
||||
});
|
||||
const indexBuilder = new IndexBuilder({
|
||||
logger: env.logger,
|
||||
searchEngine,
|
||||
});
|
||||
|
||||
const schedule = env.scheduler.createScheduledTaskRunner({
|
||||
frequency: { minutes: 10 },
|
||||
timeout: { minutes: 15 },
|
||||
// A 3 second delay gives the backend server a chance to initialize before
|
||||
// any collators are executed, which may attempt requests against the API.
|
||||
initialDelay: { seconds: 3 },
|
||||
});
|
||||
|
||||
// Collators are responsible for gathering documents known to plugins. This
|
||||
// collator gathers entities from the software catalog.
|
||||
indexBuilder.addCollator({
|
||||
schedule,
|
||||
factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
tokenManager: env.tokenManager,
|
||||
}),
|
||||
});
|
||||
|
||||
// collator gathers entities from techdocs.
|
||||
indexBuilder.addCollator({
|
||||
schedule,
|
||||
factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
logger: env.logger,
|
||||
tokenManager: env.tokenManager,
|
||||
}),
|
||||
});
|
||||
|
||||
// The scheduler controls when documents are gathered from collators and sent
|
||||
// to the search engine for indexing.
|
||||
const { scheduler } = await indexBuilder.build();
|
||||
scheduler.start();
|
||||
|
||||
useHotCleanup(module, () => scheduler.stop());
|
||||
|
||||
return await createRouter({
|
||||
engine: indexBuilder.getSearchEngine(),
|
||||
types: indexBuilder.getDocumentTypes(),
|
||||
permissions: env.permissions,
|
||||
config: env.config,
|
||||
logger: env.logger,
|
||||
});
|
||||
}
|
51
packages/backend/src/plugins/techdocs.ts
Normal file
51
packages/backend/src/plugins/techdocs.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { DockerContainerRunner } from '@backstage/backend-common';
|
||||
import {
|
||||
createRouter,
|
||||
Generators,
|
||||
Preparers,
|
||||
Publisher,
|
||||
} from '@backstage/plugin-techdocs-backend';
|
||||
import Docker from 'dockerode';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
// Preparers are responsible for fetching source files for documentation.
|
||||
const preparers = await Preparers.fromConfig(env.config, {
|
||||
logger: env.logger,
|
||||
reader: env.reader,
|
||||
});
|
||||
|
||||
// Docker client (conditionally) used by the generators, based on techdocs.generators config.
|
||||
const dockerClient = new Docker();
|
||||
const containerRunner = new DockerContainerRunner({ dockerClient });
|
||||
|
||||
// Generators are used for generating documentation sites.
|
||||
const generators = await Generators.fromConfig(env.config, {
|
||||
logger: env.logger,
|
||||
containerRunner,
|
||||
});
|
||||
|
||||
// Publisher is used for
|
||||
// 1. Publishing generated files to storage
|
||||
// 2. Fetching files from storage and passing them to TechDocs frontend.
|
||||
const publisher = await Publisher.fromConfig(env.config, {
|
||||
logger: env.logger,
|
||||
discovery: env.discovery,
|
||||
});
|
||||
|
||||
// checks if the publisher is working and logs the result
|
||||
await publisher.getReadiness();
|
||||
|
||||
return await createRouter({
|
||||
preparers,
|
||||
generators,
|
||||
publisher,
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
discovery: env.discovery,
|
||||
cache: env.cache,
|
||||
});
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
import { executeShellCommand } from '@backstage/plugin-scaffolder-node';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
class ConsoleLogStream extends Writable {
|
||||
data: string;
|
||||
|
||||
constructor(options: any) {
|
||||
super(options);
|
||||
this.data = '';
|
||||
}
|
||||
|
||||
_write(chunk: any, _: any, callback: any) {
|
||||
this.data += chunk.toString(); // Convert the chunk to a string and append it to this.data
|
||||
console.log(this.data);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
export const createVerifyDependency = () => {
|
||||
return createTemplateAction<{
|
||||
verifiers: string[];
|
||||
}>({
|
||||
id: 'cnoe:verify:dependency',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['verifiers'],
|
||||
properties: {
|
||||
verifiers: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
title: 'verifiers',
|
||||
description: 'The list of verifiers',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const verifiers = ctx.input.verifiers;
|
||||
|
||||
if (verifiers === null || verifiers.length === 0) {
|
||||
ctx.logger.error('no verifier was supplied for the object');
|
||||
return;
|
||||
}
|
||||
|
||||
const baseCommand = 'cnoe';
|
||||
const baseArguments = ['k8s', 'verify'];
|
||||
|
||||
verifiers.forEach((verifier: string) =>
|
||||
baseArguments.push('--config', verifier),
|
||||
);
|
||||
|
||||
const logStream = new ConsoleLogStream({});
|
||||
await executeShellCommand({
|
||||
command: baseCommand,
|
||||
args: baseArguments,
|
||||
logStream: logStream,
|
||||
})
|
||||
.then(() => ctx.logger.info('verification succeeded'))
|
||||
.catch(error => {
|
||||
ctx.logger.error(error);
|
||||
throw new Error(logStream.data);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -5,8 +5,9 @@ import {
|
|||
PluginDatabaseManager,
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common/dist'; //TODO: deprecated
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks/dist';
|
||||
UrlReader,
|
||||
} from '@backstage/backend-common';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
|
||||
|
@ -15,6 +16,7 @@ export type PluginEnvironment = {
|
|||
database: PluginDatabaseManager;
|
||||
cache: PluginCacheManager;
|
||||
config: Config;
|
||||
reader: UrlReader;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
scheduler: PluginTaskScheduler;
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { defineConfig } from '@playwright/test';
|
||||
import { generateProjects } from '@backstage/e2e-test-utils/playwright';
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
timeout: 60_000,
|
||||
|
||||
expect: {
|
||||
timeout: 5_000,
|
||||
},
|
||||
|
||||
// Run your local dev server before starting the tests
|
||||
webServer: process.env.CI
|
||||
? []
|
||||
: [
|
||||
{
|
||||
command: 'yarn start',
|
||||
port: 3000,
|
||||
reuseExistingServer: true,
|
||||
timeout: 60_000,
|
||||
},
|
||||
],
|
||||
|
||||
forbidOnly: !!process.env.CI,
|
||||
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
|
||||
reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]],
|
||||
|
||||
use: {
|
||||
actionTimeout: 0,
|
||||
baseURL:
|
||||
process.env.PLAYWRIGHT_URL ??
|
||||
(process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'),
|
||||
screenshot: 'only-on-failure',
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
outputDir: 'node_modules/.cache/e2e-test-results',
|
||||
|
||||
projects: generateProjects(), // Find all packages with e2e-test folders
|
||||
});
|
|
@ -4,6 +4,6 @@ This is where your own plugins and their associated modules live, each in a
|
|||
separate folder of its own.
|
||||
|
||||
If you want to create a new plugin here, go to your project root directory, run
|
||||
the command `yarn new`, and follow the on-screen instructions.
|
||||
the command `yarn backstage-cli create`, and follow the on-screen instructions.
|
||||
|
||||
You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)!
|
||||
|
|
53
plugins/cnoe-ui-plugin/package.json
Normal file
53
plugins/cnoe-ui-plugin/package.json
Normal file
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "@internal/cnoe-ui-plugin",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "frontend-plugin"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.13.3",
|
||||
"@backstage/core-plugin-api": "^1.5.3",
|
||||
"@backstage/plugin-catalog-react": "^1.8.0",
|
||||
"@backstage/plugin-home": "^0.5.4",
|
||||
"@backstage/theme": "^0.4.1",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.22.9",
|
||||
"@backstage/core-app-api": "^1.9.0",
|
||||
"@backstage/dev-utils": "^1.0.17",
|
||||
"@backstage/test-utils": "^1.4.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/node": "*",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.49.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
83
plugins/cnoe-ui-plugin/src/components/Homepage.tsx
Normal file
83
plugins/cnoe-ui-plugin/src/components/Homepage.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { Content, Page } from '@backstage/core-components';
|
||||
import { HomePageSearchBar } from '@backstage/plugin-search';
|
||||
import { SearchContextProvider } from '@backstage/plugin-search-react';
|
||||
import { Grid, makeStyles } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
HomePageToolkit,
|
||||
HomePageCompanyLogo,
|
||||
HomePageStarredEntities,
|
||||
TemplateBackstageLogoIcon,
|
||||
} from '@backstage/plugin-home';
|
||||
|
||||
import {
|
||||
LogoBig,
|
||||
} from './logos';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
searchBar: {
|
||||
display: 'flex',
|
||||
maxWidth: '60vw',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
boxShadow: theme.shadows[1],
|
||||
padding: '8px 0',
|
||||
borderRadius: '50px',
|
||||
margin: 'auto',
|
||||
},
|
||||
}));
|
||||
|
||||
const useLogoStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
margin: theme.spacing(5, 0),
|
||||
},
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 100,
|
||||
},
|
||||
path: {
|
||||
fill: '#00568c',
|
||||
},
|
||||
}));
|
||||
|
||||
export const CNOEHomepage = () => {
|
||||
const classes = useStyles();
|
||||
const { container } = useLogoStyles();
|
||||
|
||||
return (
|
||||
<SearchContextProvider>
|
||||
<Page themeId="home">
|
||||
<Content>
|
||||
<Grid container justifyContent="center" spacing={6}>
|
||||
<HomePageCompanyLogo className={container} logo={<LogoBig />} />
|
||||
<Grid container item xs={12} alignItems="center" direction="row">
|
||||
<HomePageSearchBar classes={{ root: classes.searchBar }} placeholder="Search" />
|
||||
</Grid>
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageStarredEntities />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageToolkit
|
||||
title="Quick Links"
|
||||
tools={[
|
||||
{
|
||||
url: '/catalog',
|
||||
label: 'Catalog',
|
||||
icon: <TemplateBackstageLogoIcon/>,
|
||||
},
|
||||
{
|
||||
url: '/docs',
|
||||
label: 'Tech Docs',
|
||||
icon: <TemplateBackstageLogoIcon />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
</SearchContextProvider>
|
||||
);
|
||||
};
|
56
plugins/cnoe-ui-plugin/src/components/logos/LogoBig.tsx
Normal file
56
plugins/cnoe-ui-plugin/src/components/logos/LogoBig.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 190,
|
||||
},
|
||||
whitePath: {
|
||||
fill: '#ffffff',
|
||||
stroke: 'none',
|
||||
},
|
||||
bluePath: {
|
||||
fill: '#00568c',
|
||||
stroke: 'none',
|
||||
},
|
||||
cyanPath: {
|
||||
fill: '#00adee',
|
||||
stroke: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export const LogoBig = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={classes.svg}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 2079.95 850.05"
|
||||
>
|
||||
<g><path className={classes.cyanPath} d="M 923.5,136.5 C 928.809,135.565 932.309,137.565 934,142.5C 947.791,206.068 948.457,269.735 936,333.5C 923.041,396.634 891.874,448.468 842.5,489C 832.527,492.024 828.694,488.524 831,478.5C 881.237,437.163 911.904,384.163 923,319.5C 929.867,281.3 931.201,242.966 927,204.5C 849.896,286.335 757.063,343.169 648.5,375C 560.306,401.762 470.306,412.428 378.5,407C 374.251,402.199 374.584,397.866 379.5,394C 493.995,398.085 604.329,378.751 710.5,336C 794.01,301.471 865.51,250.305 925,182.5C 924.752,176.101 923.752,169.768 922,163.5C 879.994,213.56 830.16,253.726 772.5,284C 654.362,345.618 528.696,374.618 395.5,371C 379.341,370.192 363.674,367.026 348.5,361.5C 354.504,394.851 367.67,425.184 388,452.5C 394,459.833 400.667,466.5 408,472.5C 410.323,482.507 406.489,486.007 396.5,483C 371.585,461.039 353.751,434.206 343,402.5C 336.28,383.953 331.28,364.953 328,345.5C 324.768,332.813 328.601,322.98 339.5,316C 350.829,310.223 362.829,307.89 375.5,309C 380.313,313.041 380.646,317.375 376.5,322C 366.116,323.176 356.116,325.843 346.5,330C 340.55,336.251 341.55,341.251 349.5,345C 365.81,351.729 382.81,355.396 400.5,356C 530.3,358.299 652.633,329.299 767.5,269C 829.235,236.299 881.235,192.132 923.5,136.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 659.5,164.5 C 714.308,178.702 768.974,193.536 823.5,209C 826.394,211.909 827.227,215.409 826,219.5C 823.588,222.792 820.421,223.959 816.5,223C 767.741,209.802 719.075,196.302 670.5,182.5C 669.668,192.483 669.168,202.483 669,212.5C 663.851,217.996 659.184,217.663 655,211.5C 654.333,197.5 654.333,183.5 655,169.5C 656.025,167.313 657.525,165.646 659.5,164.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 568.5,192.5 C 585.66,194.231 602.66,197.065 619.5,201C 635.167,211.333 650.833,221.667 666.5,232C 667.126,232.75 667.626,233.584 668,234.5C 668.667,257.167 668.667,279.833 668,302.5C 663.829,308.729 659.163,309.062 654,303.5C 653.833,282.825 653.333,262.159 652.5,241.5C 642.742,235.539 633.242,229.205 624,222.5C 623.667,253.833 623.333,285.167 623,316.5C 618.333,321.833 613.667,321.833 609,316.5C 608.5,282.502 608.333,248.502 608.5,214.5C 599.213,212.576 589.88,210.909 580.5,209.5C 580.667,248.168 580.5,286.835 580,325.5C 575.829,331.729 571.163,332.062 566,326.5C 565.667,288.833 565.333,251.167 565,213.5C 558.639,218.097 551.805,221.763 544.5,224.5C 537.956,223.089 535.789,219.089 538,212.5C 538.833,211.667 539.667,210.833 540.5,210C 550.066,204.392 559.399,198.559 568.5,192.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 490.5,212.5 C 504.026,214.715 517.36,217.881 530.5,222C 533.102,222.935 534.602,224.768 535,227.5C 535.667,263.167 535.667,298.833 535,334.5C 532.567,340.448 528.4,341.948 522.5,339C 521.107,337.829 520.273,336.329 520,334.5C 519.5,301.168 519.333,267.835 519.5,234.5C 512.102,232.981 504.769,231.481 497.5,230C 492.207,238.751 487.041,247.585 482,256.5C 481.667,285.833 481.333,315.167 481,344.5C 478.612,349.226 474.778,350.726 469.5,349C 468.667,348.167 467.833,347.333 467,346.5C 466.333,314.833 466.333,283.167 467,251.5C 474.342,238.14 482.175,225.14 490.5,212.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 432.5,231.5 C 442.172,231.334 451.839,231.5 461.5,232C 467.259,235.892 467.926,240.559 463.5,246C 456.254,247.391 448.921,247.891 441.5,247.5C 441.667,280.502 441.5,313.502 441,346.5C 437.004,351.3 432.67,351.633 428,347.5C 426.346,310.585 426.013,273.585 427,236.5C 428.107,233.887 429.94,232.22 432.5,231.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 392.5,265.5 C 400.507,265.334 408.507,265.5 416.5,266C 424.5,271 424.5,276 416.5,281C 411.511,281.499 406.511,281.666 401.5,281.5C 401.667,302.503 401.5,323.503 401,344.5C 396.333,349.833 391.667,349.833 387,344.5C 386.333,319.5 386.333,294.5 387,269.5C 388.5,267.531 390.333,266.198 392.5,265.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 898.5,452.5 C 942.835,452.333 987.168,452.5 1031.5,453C 1034.37,454.393 1035.7,456.726 1035.5,460C 1035.7,463.274 1034.37,465.607 1031.5,467C 987.167,467.667 942.833,467.667 898.5,467C 895.634,465.607 894.301,463.274 894.5,460C 894.43,456.634 895.763,454.134 898.5,452.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 243.5,464.5 C 285.501,464.333 327.501,464.5 369.5,465C 372.366,466.393 373.699,468.726 373.5,472C 373.699,475.274 372.366,477.607 369.5,479C 327.5,479.667 285.5,479.667 243.5,479C 240.484,477.471 239.151,474.971 239.5,471.5C 239.197,468.156 240.53,465.822 243.5,464.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 925.5,479.5 C 944.503,479.333 963.503,479.5 982.5,480C 988.509,484.314 988.843,488.981 983.5,494C 963.5,494.667 943.5,494.667 923.5,494C 919.002,488.32 919.669,483.487 925.5,479.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 235.5,490.5 C 260.502,490.333 285.502,490.5 310.5,491C 314.136,493.039 315.802,496.206 315.5,500.5C 404.501,500.333 493.501,500.5 582.5,501C 588.729,505.171 589.062,509.837 583.5,515C 490.167,515.667 396.833,515.667 303.5,515C 300.38,512.592 299.047,509.426 299.5,505.5C 277.831,505.667 256.164,505.5 234.5,505C 231.606,502.091 230.773,498.591 232,494.5C 232.69,492.65 233.856,491.316 235.5,490.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 614.5,498.5 C 714.167,498.333 813.834,498.5 913.5,499C 918.535,502.013 919.702,506.18 917,511.5C 916.25,512.126 915.416,512.626 914.5,513C 814.167,513.667 713.833,513.667 613.5,513C 608.003,507.683 608.336,502.85 614.5,498.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 566.5,526.5 C 588.169,526.333 609.836,526.5 631.5,527C 637.786,529.362 639.453,533.529 636.5,539.5C 635.335,540.584 634.002,541.417 632.5,542C 610.167,542.667 587.833,542.667 565.5,542C 560.774,539.612 559.274,535.778 561,530.5C 562.5,528.531 564.333,527.198 566.5,526.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 276.5,527.5 C 314.501,527.333 352.501,527.5 390.5,528C 396.552,532.223 396.885,536.889 391.5,542C 353.167,542.667 314.833,542.667 276.5,542C 272.788,539.487 271.622,535.987 273,531.5C 274.376,530.295 275.542,528.962 276.5,527.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 738.5,532.5 C 777.168,532.333 815.835,532.5 854.5,533C 860.062,538.163 859.729,542.829 853.5,547C 815.167,547.667 776.833,547.667 738.5,547C 735.634,545.607 734.301,543.274 734.5,540C 734.43,536.634 735.763,534.134 738.5,532.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 355.5,556.5 C 438.167,556.333 520.834,556.5 603.5,557C 609.075,562.115 608.742,566.781 602.5,571C 520.5,571.667 438.5,571.667 356.5,571C 350.367,566.613 350.033,561.78 355.5,556.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 844.5,558.5 C 880.502,558.333 916.502,558.5 952.5,559C 957.833,563.667 957.833,568.333 952.5,573C 916.833,573.667 881.167,573.667 845.5,573C 839.231,568.657 838.898,563.824 844.5,558.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 197.5,608.5 C 245.833,608.5 294.167,608.5 342.5,608.5C 342.5,622.5 342.5,636.5 342.5,650.5C 296.832,650.333 251.165,650.5 205.5,651C 192.103,652.4 183.603,659.566 180,672.5C 178,698.5 178,724.5 180,750.5C 183.451,762.947 191.618,770.113 204.5,772C 250.499,772.5 296.499,772.667 342.5,772.5C 342.5,786.5 342.5,800.5 342.5,814.5C 293.499,814.667 244.499,814.5 195.5,814C 161.97,812.133 142.803,794.633 138,761.5C 137.333,728.167 137.333,694.833 138,661.5C 140.962,636.219 154.462,619.719 178.5,612C 184.933,610.597 191.267,609.43 197.5,608.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 399.5,608.5 C 413.837,608.333 428.171,608.5 442.5,609C 483.022,655.888 523.189,703.054 563,750.5C 563.5,703.168 563.667,655.835 563.5,608.5C 577.5,608.5 591.5,608.5 605.5,608.5C 605.5,677.167 605.5,745.833 605.5,814.5C 591.163,814.667 576.829,814.5 562.5,814C 522.215,767.26 482.048,720.426 442,673.5C 441.5,720.499 441.333,767.499 441.5,814.5C 427.5,814.5 413.5,814.5 399.5,814.5C 399.5,745.833 399.5,677.167 399.5,608.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 722.5,608.5 C 753.86,608.071 785.193,608.571 816.5,610C 848.538,615.71 865.371,634.876 867,667.5C 867.667,696.833 867.667,726.167 867,755.5C 864.167,791.667 844.667,811.167 808.5,814C 778.833,814.667 749.167,814.667 719.5,814C 683.4,810.566 664.233,790.732 662,754.5C 661.333,725.833 661.333,697.167 662,668.5C 664.815,630.851 684.982,610.851 722.5,608.5 Z M 725.5,650.5 C 749.502,650.333 773.502,650.5 797.5,651C 813.546,652.713 822.712,661.546 825,677.5C 825.667,700.167 825.667,722.833 825,745.5C 822.833,761 814,769.833 798.5,772C 775.833,772.667 753.167,772.667 730.5,772C 714.546,769.712 705.713,760.546 704,744.5C 703.333,722.167 703.333,699.833 704,677.5C 704.565,662.953 711.732,653.953 725.5,650.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 924.5,608.5 C 992.833,608.5 1061.17,608.5 1129.5,608.5C 1129.5,622.5 1129.5,636.5 1129.5,650.5C 1061.17,650.5 992.833,650.5 924.5,650.5C 924.5,636.5 924.5,622.5 924.5,608.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 924.5,690.5 C 992.833,690.5 1061.17,690.5 1129.5,690.5C 1129.5,704.5 1129.5,718.5 1129.5,732.5C 1061.17,732.5 992.833,732.5 924.5,732.5C 924.5,718.5 924.5,704.5 924.5,690.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 924.5,772.5 C 992.833,772.5 1061.17,772.5 1129.5,772.5C 1129.5,786.5 1129.5,800.5 1129.5,814.5C 1061.17,814.5 992.833,814.5 924.5,814.5C 924.5,800.5 924.5,786.5 924.5,772.5 Z"/></g>
|
||||
</svg>
|
||||
);
|
||||
};
|
56
plugins/cnoe-ui-plugin/src/components/logos/LogoFull.tsx
Normal file
56
plugins/cnoe-ui-plugin/src/components/logos/LogoFull.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 90,
|
||||
},
|
||||
whitePath: {
|
||||
fill: '#ffffff',
|
||||
stroke: 'none',
|
||||
},
|
||||
bluePath: {
|
||||
fill: '#00568c',
|
||||
stroke: 'none',
|
||||
},
|
||||
cyanPath: {
|
||||
fill: '#00adee',
|
||||
stroke: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export const LogoFull = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={classes.svg}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 2079.95 850.05"
|
||||
>
|
||||
<g><path className={classes.cyanPath} d="M 923.5,136.5 C 928.809,135.565 932.309,137.565 934,142.5C 947.791,206.068 948.457,269.735 936,333.5C 923.041,396.634 891.874,448.468 842.5,489C 832.527,492.024 828.694,488.524 831,478.5C 881.237,437.163 911.904,384.163 923,319.5C 929.867,281.3 931.201,242.966 927,204.5C 849.896,286.335 757.063,343.169 648.5,375C 560.306,401.762 470.306,412.428 378.5,407C 374.251,402.199 374.584,397.866 379.5,394C 493.995,398.085 604.329,378.751 710.5,336C 794.01,301.471 865.51,250.305 925,182.5C 924.752,176.101 923.752,169.768 922,163.5C 879.994,213.56 830.16,253.726 772.5,284C 654.362,345.618 528.696,374.618 395.5,371C 379.341,370.192 363.674,367.026 348.5,361.5C 354.504,394.851 367.67,425.184 388,452.5C 394,459.833 400.667,466.5 408,472.5C 410.323,482.507 406.489,486.007 396.5,483C 371.585,461.039 353.751,434.206 343,402.5C 336.28,383.953 331.28,364.953 328,345.5C 324.768,332.813 328.601,322.98 339.5,316C 350.829,310.223 362.829,307.89 375.5,309C 380.313,313.041 380.646,317.375 376.5,322C 366.116,323.176 356.116,325.843 346.5,330C 340.55,336.251 341.55,341.251 349.5,345C 365.81,351.729 382.81,355.396 400.5,356C 530.3,358.299 652.633,329.299 767.5,269C 829.235,236.299 881.235,192.132 923.5,136.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 659.5,164.5 C 714.308,178.702 768.974,193.536 823.5,209C 826.394,211.909 827.227,215.409 826,219.5C 823.588,222.792 820.421,223.959 816.5,223C 767.741,209.802 719.075,196.302 670.5,182.5C 669.668,192.483 669.168,202.483 669,212.5C 663.851,217.996 659.184,217.663 655,211.5C 654.333,197.5 654.333,183.5 655,169.5C 656.025,167.313 657.525,165.646 659.5,164.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 568.5,192.5 C 585.66,194.231 602.66,197.065 619.5,201C 635.167,211.333 650.833,221.667 666.5,232C 667.126,232.75 667.626,233.584 668,234.5C 668.667,257.167 668.667,279.833 668,302.5C 663.829,308.729 659.163,309.062 654,303.5C 653.833,282.825 653.333,262.159 652.5,241.5C 642.742,235.539 633.242,229.205 624,222.5C 623.667,253.833 623.333,285.167 623,316.5C 618.333,321.833 613.667,321.833 609,316.5C 608.5,282.502 608.333,248.502 608.5,214.5C 599.213,212.576 589.88,210.909 580.5,209.5C 580.667,248.168 580.5,286.835 580,325.5C 575.829,331.729 571.163,332.062 566,326.5C 565.667,288.833 565.333,251.167 565,213.5C 558.639,218.097 551.805,221.763 544.5,224.5C 537.956,223.089 535.789,219.089 538,212.5C 538.833,211.667 539.667,210.833 540.5,210C 550.066,204.392 559.399,198.559 568.5,192.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 490.5,212.5 C 504.026,214.715 517.36,217.881 530.5,222C 533.102,222.935 534.602,224.768 535,227.5C 535.667,263.167 535.667,298.833 535,334.5C 532.567,340.448 528.4,341.948 522.5,339C 521.107,337.829 520.273,336.329 520,334.5C 519.5,301.168 519.333,267.835 519.5,234.5C 512.102,232.981 504.769,231.481 497.5,230C 492.207,238.751 487.041,247.585 482,256.5C 481.667,285.833 481.333,315.167 481,344.5C 478.612,349.226 474.778,350.726 469.5,349C 468.667,348.167 467.833,347.333 467,346.5C 466.333,314.833 466.333,283.167 467,251.5C 474.342,238.14 482.175,225.14 490.5,212.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 432.5,231.5 C 442.172,231.334 451.839,231.5 461.5,232C 467.259,235.892 467.926,240.559 463.5,246C 456.254,247.391 448.921,247.891 441.5,247.5C 441.667,280.502 441.5,313.502 441,346.5C 437.004,351.3 432.67,351.633 428,347.5C 426.346,310.585 426.013,273.585 427,236.5C 428.107,233.887 429.94,232.22 432.5,231.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 392.5,265.5 C 400.507,265.334 408.507,265.5 416.5,266C 424.5,271 424.5,276 416.5,281C 411.511,281.499 406.511,281.666 401.5,281.5C 401.667,302.503 401.5,323.503 401,344.5C 396.333,349.833 391.667,349.833 387,344.5C 386.333,319.5 386.333,294.5 387,269.5C 388.5,267.531 390.333,266.198 392.5,265.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 898.5,452.5 C 942.835,452.333 987.168,452.5 1031.5,453C 1034.37,454.393 1035.7,456.726 1035.5,460C 1035.7,463.274 1034.37,465.607 1031.5,467C 987.167,467.667 942.833,467.667 898.5,467C 895.634,465.607 894.301,463.274 894.5,460C 894.43,456.634 895.763,454.134 898.5,452.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 243.5,464.5 C 285.501,464.333 327.501,464.5 369.5,465C 372.366,466.393 373.699,468.726 373.5,472C 373.699,475.274 372.366,477.607 369.5,479C 327.5,479.667 285.5,479.667 243.5,479C 240.484,477.471 239.151,474.971 239.5,471.5C 239.197,468.156 240.53,465.822 243.5,464.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 925.5,479.5 C 944.503,479.333 963.503,479.5 982.5,480C 988.509,484.314 988.843,488.981 983.5,494C 963.5,494.667 943.5,494.667 923.5,494C 919.002,488.32 919.669,483.487 925.5,479.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 235.5,490.5 C 260.502,490.333 285.502,490.5 310.5,491C 314.136,493.039 315.802,496.206 315.5,500.5C 404.501,500.333 493.501,500.5 582.5,501C 588.729,505.171 589.062,509.837 583.5,515C 490.167,515.667 396.833,515.667 303.5,515C 300.38,512.592 299.047,509.426 299.5,505.5C 277.831,505.667 256.164,505.5 234.5,505C 231.606,502.091 230.773,498.591 232,494.5C 232.69,492.65 233.856,491.316 235.5,490.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 614.5,498.5 C 714.167,498.333 813.834,498.5 913.5,499C 918.535,502.013 919.702,506.18 917,511.5C 916.25,512.126 915.416,512.626 914.5,513C 814.167,513.667 713.833,513.667 613.5,513C 608.003,507.683 608.336,502.85 614.5,498.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 566.5,526.5 C 588.169,526.333 609.836,526.5 631.5,527C 637.786,529.362 639.453,533.529 636.5,539.5C 635.335,540.584 634.002,541.417 632.5,542C 610.167,542.667 587.833,542.667 565.5,542C 560.774,539.612 559.274,535.778 561,530.5C 562.5,528.531 564.333,527.198 566.5,526.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 276.5,527.5 C 314.501,527.333 352.501,527.5 390.5,528C 396.552,532.223 396.885,536.889 391.5,542C 353.167,542.667 314.833,542.667 276.5,542C 272.788,539.487 271.622,535.987 273,531.5C 274.376,530.295 275.542,528.962 276.5,527.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 738.5,532.5 C 777.168,532.333 815.835,532.5 854.5,533C 860.062,538.163 859.729,542.829 853.5,547C 815.167,547.667 776.833,547.667 738.5,547C 735.634,545.607 734.301,543.274 734.5,540C 734.43,536.634 735.763,534.134 738.5,532.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 355.5,556.5 C 438.167,556.333 520.834,556.5 603.5,557C 609.075,562.115 608.742,566.781 602.5,571C 520.5,571.667 438.5,571.667 356.5,571C 350.367,566.613 350.033,561.78 355.5,556.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 844.5,558.5 C 880.502,558.333 916.502,558.5 952.5,559C 957.833,563.667 957.833,568.333 952.5,573C 916.833,573.667 881.167,573.667 845.5,573C 839.231,568.657 838.898,563.824 844.5,558.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 197.5,608.5 C 245.833,608.5 294.167,608.5 342.5,608.5C 342.5,622.5 342.5,636.5 342.5,650.5C 296.832,650.333 251.165,650.5 205.5,651C 192.103,652.4 183.603,659.566 180,672.5C 178,698.5 178,724.5 180,750.5C 183.451,762.947 191.618,770.113 204.5,772C 250.499,772.5 296.499,772.667 342.5,772.5C 342.5,786.5 342.5,800.5 342.5,814.5C 293.499,814.667 244.499,814.5 195.5,814C 161.97,812.133 142.803,794.633 138,761.5C 137.333,728.167 137.333,694.833 138,661.5C 140.962,636.219 154.462,619.719 178.5,612C 184.933,610.597 191.267,609.43 197.5,608.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 399.5,608.5 C 413.837,608.333 428.171,608.5 442.5,609C 483.022,655.888 523.189,703.054 563,750.5C 563.5,703.168 563.667,655.835 563.5,608.5C 577.5,608.5 591.5,608.5 605.5,608.5C 605.5,677.167 605.5,745.833 605.5,814.5C 591.163,814.667 576.829,814.5 562.5,814C 522.215,767.26 482.048,720.426 442,673.5C 441.5,720.499 441.333,767.499 441.5,814.5C 427.5,814.5 413.5,814.5 399.5,814.5C 399.5,745.833 399.5,677.167 399.5,608.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 722.5,608.5 C 753.86,608.071 785.193,608.571 816.5,610C 848.538,615.71 865.371,634.876 867,667.5C 867.667,696.833 867.667,726.167 867,755.5C 864.167,791.667 844.667,811.167 808.5,814C 778.833,814.667 749.167,814.667 719.5,814C 683.4,810.566 664.233,790.732 662,754.5C 661.333,725.833 661.333,697.167 662,668.5C 664.815,630.851 684.982,610.851 722.5,608.5 Z M 725.5,650.5 C 749.502,650.333 773.502,650.5 797.5,651C 813.546,652.713 822.712,661.546 825,677.5C 825.667,700.167 825.667,722.833 825,745.5C 822.833,761 814,769.833 798.5,772C 775.833,772.667 753.167,772.667 730.5,772C 714.546,769.712 705.713,760.546 704,744.5C 703.333,722.167 703.333,699.833 704,677.5C 704.565,662.953 711.732,653.953 725.5,650.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 924.5,608.5 C 992.833,608.5 1061.17,608.5 1129.5,608.5C 1129.5,622.5 1129.5,636.5 1129.5,650.5C 1061.17,650.5 992.833,650.5 924.5,650.5C 924.5,636.5 924.5,622.5 924.5,608.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 924.5,690.5 C 992.833,690.5 1061.17,690.5 1129.5,690.5C 1129.5,704.5 1129.5,718.5 1129.5,732.5C 1061.17,732.5 992.833,732.5 924.5,732.5C 924.5,718.5 924.5,704.5 924.5,690.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 924.5,772.5 C 992.833,772.5 1061.17,772.5 1129.5,772.5C 1129.5,786.5 1129.5,800.5 1129.5,814.5C 1061.17,814.5 992.833,814.5 924.5,814.5C 924.5,800.5 924.5,786.5 924.5,772.5 Z"/></g>
|
||||
</svg>
|
||||
);
|
||||
};
|
44
plugins/cnoe-ui-plugin/src/components/logos/LogoIcon.tsx
Normal file
44
plugins/cnoe-ui-plugin/src/components/logos/LogoIcon.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 28,
|
||||
},
|
||||
whitePath: {
|
||||
fill: '#ffffff',
|
||||
stroke: 'none',
|
||||
},
|
||||
bluePath: {
|
||||
fill: '#00568c',
|
||||
stroke: 'none',
|
||||
},
|
||||
cyanPath: {
|
||||
fill: '#00adee',
|
||||
stroke: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export const LogoIcon = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={classes.svg}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 337.46 428.5"
|
||||
>
|
||||
<g><path className={classes.cyanPath} d="M 923.5,136.5 C 928.809,135.565 932.309,137.565 934,142.5C 947.791,206.068 948.457,269.735 936,333.5C 923.041,396.634 891.874,448.468 842.5,489C 832.527,492.024 828.694,488.524 831,478.5C 881.237,437.163 911.904,384.163 923,319.5C 929.867,281.3 931.201,242.966 927,204.5C 849.896,286.335 757.063,343.169 648.5,375C 560.306,401.762 470.306,412.428 378.5,407C 374.251,402.199 374.584,397.866 379.5,394C 493.995,398.085 604.329,378.751 710.5,336C 794.01,301.471 865.51,250.305 925,182.5C 924.752,176.101 923.752,169.768 922,163.5C 879.994,213.56 830.16,253.726 772.5,284C 654.362,345.618 528.696,374.618 395.5,371C 379.341,370.192 363.674,367.026 348.5,361.5C 354.504,394.851 367.67,425.184 388,452.5C 394,459.833 400.667,466.5 408,472.5C 410.323,482.507 406.489,486.007 396.5,483C 371.585,461.039 353.751,434.206 343,402.5C 336.28,383.953 331.28,364.953 328,345.5C 324.768,332.813 328.601,322.98 339.5,316C 350.829,310.223 362.829,307.89 375.5,309C 380.313,313.041 380.646,317.375 376.5,322C 366.116,323.176 356.116,325.843 346.5,330C 340.55,336.251 341.55,341.251 349.5,345C 365.81,351.729 382.81,355.396 400.5,356C 530.3,358.299 652.633,329.299 767.5,269C 829.235,236.299 881.235,192.132 923.5,136.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 659.5,164.5 C 714.308,178.702 768.974,193.536 823.5,209C 826.394,211.909 827.227,215.409 826,219.5C 823.588,222.792 820.421,223.959 816.5,223C 767.741,209.802 719.075,196.302 670.5,182.5C 669.668,192.483 669.168,202.483 669,212.5C 663.851,217.996 659.184,217.663 655,211.5C 654.333,197.5 654.333,183.5 655,169.5C 656.025,167.313 657.525,165.646 659.5,164.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 568.5,192.5 C 585.66,194.231 602.66,197.065 619.5,201C 635.167,211.333 650.833,221.667 666.5,232C 667.126,232.75 667.626,233.584 668,234.5C 668.667,257.167 668.667,279.833 668,302.5C 663.829,308.729 659.163,309.062 654,303.5C 653.833,282.825 653.333,262.159 652.5,241.5C 642.742,235.539 633.242,229.205 624,222.5C 623.667,253.833 623.333,285.167 623,316.5C 618.333,321.833 613.667,321.833 609,316.5C 608.5,282.502 608.333,248.502 608.5,214.5C 599.213,212.576 589.88,210.909 580.5,209.5C 580.667,248.168 580.5,286.835 580,325.5C 575.829,331.729 571.163,332.062 566,326.5C 565.667,288.833 565.333,251.167 565,213.5C 558.639,218.097 551.805,221.763 544.5,224.5C 537.956,223.089 535.789,219.089 538,212.5C 538.833,211.667 539.667,210.833 540.5,210C 550.066,204.392 559.399,198.559 568.5,192.5 Z"/></g>
|
||||
<g><path className={classes.cyanPath} d="M 490.5,212.5 C 504.026,214.715 517.36,217.881 530.5,222C 533.102,222.935 534.602,224.768 535,227.5C 535.667,263.167 535.667,298.833 535,334.5C 532.567,340.448 528.4,341.948 522.5,339C 521.107,337.829 520.273,336.329 520,334.5C 519.5,301.168 519.333,267.835 519.5,234.5C 512.102,232.981 504.769,231.481 497.5,230C 492.207,238.751 487.041,247.585 482,256.5C 481.667,285.833 481.333,315.167 481,344.5C 478.612,349.226 474.778,350.726 469.5,349C 468.667,348.167 467.833,347.333 467,346.5C 466.333,314.833 466.333,283.167 467,251.5C 474.342,238.14 482.175,225.14 490.5,212.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 432.5,231.5 C 442.172,231.334 451.839,231.5 461.5,232C 467.259,235.892 467.926,240.559 463.5,246C 456.254,247.391 448.921,247.891 441.5,247.5C 441.667,280.502 441.5,313.502 441,346.5C 437.004,351.3 432.67,351.633 428,347.5C 426.346,310.585 426.013,273.585 427,236.5C 428.107,233.887 429.94,232.22 432.5,231.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 392.5,265.5 C 400.507,265.334 408.507,265.5 416.5,266C 424.5,271 424.5,276 416.5,281C 411.511,281.499 406.511,281.666 401.5,281.5C 401.667,302.503 401.5,323.503 401,344.5C 396.333,349.833 391.667,349.833 387,344.5C 386.333,319.5 386.333,294.5 387,269.5C 388.5,267.531 390.333,266.198 392.5,265.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 898.5,452.5 C 942.835,452.333 987.168,452.5 1031.5,453C 1034.37,454.393 1035.7,456.726 1035.5,460C 1035.7,463.274 1034.37,465.607 1031.5,467C 987.167,467.667 942.833,467.667 898.5,467C 895.634,465.607 894.301,463.274 894.5,460C 894.43,456.634 895.763,454.134 898.5,452.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 243.5,464.5 C 285.501,464.333 327.501,464.5 369.5,465C 372.366,466.393 373.699,468.726 373.5,472C 373.699,475.274 372.366,477.607 369.5,479C 327.5,479.667 285.5,479.667 243.5,479C 240.484,477.471 239.151,474.971 239.5,471.5C 239.197,468.156 240.53,465.822 243.5,464.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 925.5,479.5 C 944.503,479.333 963.503,479.5 982.5,480C 988.509,484.314 988.843,488.981 983.5,494C 963.5,494.667 943.5,494.667 923.5,494C 919.002,488.32 919.669,483.487 925.5,479.5 Z"/></g>
|
||||
<g><path className={classes.bluePath} d="M 235.5,490.5 C 260.502,490.333 285.502,490.5 310.5,491C 314.136,493.039 315.802,496.206 315.5,500.5C 404.501,500.333 493.501,500.5 582.5,501C 588.729,505.171 589.062,509.837 583.5,515C 490.167,515.667 396.833,515.667 303.5,515C 300.38,512.592 299.047,509.426 299.5,505.5C 277.831,505.667 256.164,505.5 234.5,505C 231.606,502.091 230.773,498.591 232,494.5C 232.69,492.65 233.856,491.316 235.5,490.5 Z"/></g>
|
||||
</svg>
|
||||
);
|
||||
};
|
3
plugins/cnoe-ui-plugin/src/components/logos/index.tsx
Normal file
3
plugins/cnoe-ui-plugin/src/components/logos/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export {LogoFull} from './LogoFull';
|
||||
export {LogoIcon} from './LogoIcon';
|
||||
export {LogoBig} from './LogoBig';
|
38
plugins/cnoe-ui-plugin/src/components/themes/dark-theme.ts
Normal file
38
plugins/cnoe-ui-plugin/src/components/themes/dark-theme.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import {createTheme, darkTheme, genPageTheme, shapes} from '@backstage/theme';
|
||||
|
||||
export const cnoeDarkTheme = createTheme({
|
||||
palette: {
|
||||
...darkTheme.palette,
|
||||
primary: {
|
||||
main: '#25a0c2',
|
||||
},
|
||||
secondary: {
|
||||
main: '#00568c',
|
||||
},
|
||||
},
|
||||
defaultPageTheme: 'home',
|
||||
pageTheme: {
|
||||
home: genPageTheme({colors: ['#25a0c2', '#00568c'], shape: shapes.wave}),
|
||||
documentation: genPageTheme({
|
||||
colors: ['#25a0c2', '#00568c'],
|
||||
shape: shapes.wave2,
|
||||
}),
|
||||
tool: genPageTheme({colors: ['#25a0c2', '#00568c'], shape: shapes.round}),
|
||||
service: genPageTheme({
|
||||
colors: ['#25a0c2', '#00568c'],
|
||||
shape: shapes.wave,
|
||||
}),
|
||||
website: genPageTheme({
|
||||
colors: ['#25a0c2', '#00568c'],
|
||||
shape: shapes.wave,
|
||||
}),
|
||||
library: genPageTheme({
|
||||
colors: ['#25a0c2', '#00568c'],
|
||||
shape: shapes.wave,
|
||||
}),
|
||||
other: genPageTheme({colors: ['#25a0c2', '#00568c'], shape: shapes.wave}),
|
||||
app: genPageTheme({colors: ['#25a0c2', '#00568c'], shape: shapes.wave}),
|
||||
apis: genPageTheme({colors: ['#25a0c2', '#00568c'], shape: shapes.wave}),
|
||||
},
|
||||
});
|
||||
|
2
plugins/cnoe-ui-plugin/src/components/themes/index.ts
Normal file
2
plugins/cnoe-ui-plugin/src/components/themes/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export {cnoeDarkTheme} from './dark-theme';
|
||||
export {cnoeLightTheme} from './light-theme';
|
39
plugins/cnoe-ui-plugin/src/components/themes/light-theme.ts
Normal file
39
plugins/cnoe-ui-plugin/src/components/themes/light-theme.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import {createTheme, lightTheme, genPageTheme, shapes} from '@backstage/theme';
|
||||
|
||||
export const cnoeLightTheme = createTheme({
|
||||
palette: {
|
||||
...lightTheme.palette,
|
||||
primary: {
|
||||
main: '#00568c',
|
||||
},
|
||||
secondary: {
|
||||
main: '#00adee',
|
||||
},
|
||||
},
|
||||
defaultPageTheme: 'home',
|
||||
pageTheme: {
|
||||
home: genPageTheme({colors: ['#00568c', '#00adee'], shape: shapes.wave}),
|
||||
documentation: genPageTheme({
|
||||
colors: ['#00568c', '#00adee'],
|
||||
shape: shapes.wave2,
|
||||
}),
|
||||
tool: genPageTheme({colors: ['#00568c', '#00adee'], shape: shapes.round}),
|
||||
service: genPageTheme({
|
||||
colors: ['#00568c', '#00adee'],
|
||||
shape: shapes.wave,
|
||||
}),
|
||||
website: genPageTheme({
|
||||
colors: ['#00568c', '#00adee'],
|
||||
shape: shapes.wave,
|
||||
}),
|
||||
library: genPageTheme({
|
||||
colors: ['#00568c', '#00adee'],
|
||||
shape: shapes.wave,
|
||||
}),
|
||||
other: genPageTheme({colors: ['#00568c', '#00adee'], shape: shapes.wave}),
|
||||
app: genPageTheme({colors: ['#00568c', '#00adee'], shape: shapes.wave}),
|
||||
apis: genPageTheme({colors: ['#00568c', '#00adee'], shape: shapes.wave}),
|
||||
},
|
||||
});
|
||||
|
||||
|
3
plugins/cnoe-ui-plugin/src/index.ts
Normal file
3
plugins/cnoe-ui-plugin/src/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './components/themes';
|
||||
export {LogoFull, LogoIcon} from './components/logos';
|
||||
export {CNOEHomepage} from './components/Homepage';
|
36
plugins/cnoe-ui-plugin/src/plugin.ts
Normal file
36
plugins/cnoe-ui-plugin/src/plugin.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import {createPlugin, createComponentExtension} from '@backstage/core-plugin-api';
|
||||
|
||||
import {rootRouteRef} from './routes';
|
||||
|
||||
export const cnoeFrontendPlugin = createPlugin({
|
||||
id: 'cnoe-ui-plugin',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const AWSLogoFull = cnoeFrontendPlugin
|
||||
.provide(
|
||||
createComponentExtension({
|
||||
name: 'LogoFull',
|
||||
component: {lazy: () => import('./components/logos/LogoFull').then(m => m.LogoFull)},
|
||||
}),
|
||||
);
|
||||
|
||||
export const AWSLogoIcon = cnoeFrontendPlugin
|
||||
.provide(
|
||||
createComponentExtension({
|
||||
name: 'LogoIcon',
|
||||
component: {lazy: () => import('./components/logos/LogoIcon').then(m => m.LogoIcon)},
|
||||
}),
|
||||
);
|
||||
|
||||
export const CNOEHomepage = cnoeFrontendPlugin
|
||||
.provide(
|
||||
createComponentExtension({
|
||||
name: 'Homepage',
|
||||
component: {
|
||||
lazy: () => import('./components/Homepage').then(m => m.CNOEHomepage),
|
||||
},
|
||||
}),
|
||||
);
|
5
plugins/cnoe-ui-plugin/src/routes.ts
Normal file
5
plugins/cnoe-ui-plugin/src/routes.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import {createRouteRef} from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'cnoe-ui',
|
||||
});
|
1
plugins/workflows/.eslintrc.js
Normal file
1
plugins/workflows/.eslintrc.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
13
plugins/workflows/README.md
Normal file
13
plugins/workflows/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# workflows
|
||||
|
||||
Welcome to the workflows plugin!
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
|
||||
## Getting started
|
||||
|
||||
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/workflows](http://localhost:3000/workflows).
|
||||
|
||||
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
|
||||
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
|
||||
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
|
53
plugins/workflows/package.json
Normal file
53
plugins/workflows/package.json
Normal file
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "@internal/plugin-workflows",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "frontend-plugin"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.13.3",
|
||||
"@backstage/core-plugin-api": "^1.5.3",
|
||||
"@backstage/plugin-catalog-react": "^1.8.0",
|
||||
"@backstage/theme": "^0.4.1",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.22.9",
|
||||
"@backstage/core-app-api": "^1.9.0",
|
||||
"@backstage/dev-utils": "^1.0.17",
|
||||
"@backstage/test-utils": "^1.4.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/node": "*",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.49.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// import React from 'react';
|
||||
// import { rest } from 'msw';
|
||||
// import { setupServer } from 'msw/node';
|
||||
// import { screen } from '@testing-library/react';
|
||||
// import {
|
||||
// setupRequestMockHandlers,
|
||||
// renderInTestApp,
|
||||
// } from "@backstage/test-utils";
|
||||
|
||||
// describe('ExampleComponent', () => {
|
||||
// const server = setupServer();
|
||||
// // Enable sane handlers for network requests
|
||||
// setupRequestMockHandlers(server);
|
||||
//
|
||||
// // setup mock response
|
||||
// beforeEach(() => {
|
||||
// server.use(
|
||||
// rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
|
||||
// );
|
||||
// });
|
||||
//
|
||||
// it('should render', async () => {
|
||||
// await renderInTestApp(<ExampleComponent />);
|
||||
// expect(screen.getByText('Welcome to workflows!')).toBeInTheDocument();
|
||||
// });
|
||||
// });
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import {Grid} from '@material-ui/core';
|
||||
import {
|
||||
Page,
|
||||
Content,
|
||||
} from '@backstage/core-components';
|
||||
import {FetchTFState, ManageBlueprint} from "./FetchTFState";
|
||||
|
||||
export const BlueprintsComponent = () => (
|
||||
<Page themeId="tool">
|
||||
<Content>
|
||||
{/* <ContentHeader title="Blueprint information">*/}
|
||||
{/* <SupportButton>A description of your plugin goes here.</SupportButton>*/}
|
||||
{/* </ContentHeader>*/}
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item>
|
||||
<ManageBlueprint />
|
||||
{/* <InfoCard title="Blueprint management">*/}
|
||||
{/* <Typography color="textSecondary">*/}
|
||||
{/* Manage this blueprint deployment*/}
|
||||
{/* </Typography>*/}
|
||||
{/* <IconButton aria-label="delete" size="medium">*/}
|
||||
{/* <DeleteIcon />*/}
|
||||
{/* </IconButton>*/}
|
||||
{/* <IconButton aria-label="clear" size="medium">*/}
|
||||
{/* <ClearIcon />*/}
|
||||
{/* </IconButton>*/}
|
||||
{/* <IconButton aria-label="link" size="medium">*/}
|
||||
{/* <LinkOffRounded />*/}
|
||||
{/* </IconButton>*/}
|
||||
{/* </InfoCard>*/}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FetchTFState />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
|
@ -0,0 +1,353 @@
|
|||
import React, {useState} from 'react';
|
||||
import {Table, TableColumn, Progress, InfoCard, LinkButton} from '@backstage/core-components';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
import {
|
||||
DiscoveryApi,
|
||||
discoveryApiRef, OpenIdConnectApi,
|
||||
useApi
|
||||
} from '@backstage/core-plugin-api';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {gunzipSync} from "zlib";
|
||||
import {useEntity} from "@backstage/plugin-catalog-react"
|
||||
import {
|
||||
Dialog, DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import ClearIcon from "@material-ui/icons/Clear";
|
||||
import LinkOffRounded from "@material-ui/icons/LinkOffRounded";
|
||||
import {keycloakOIDCAuthApiRef} from "../../plugin";
|
||||
|
||||
type TFState = {
|
||||
terraform_version?: string
|
||||
resources: {
|
||||
name: string
|
||||
provider: string
|
||||
type: string
|
||||
instances: {
|
||||
attributes: {
|
||||
arn: string
|
||||
id: string
|
||||
}
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
|
||||
type Resource = {
|
||||
name: string
|
||||
provider: string
|
||||
type: string
|
||||
arn?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
type TFTableProps = {
|
||||
resources: Resource[]
|
||||
}
|
||||
|
||||
export const TFTable = (props: TFTableProps) => {
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{ title: 'Name', field: 'name' },
|
||||
{ title: 'Provider', field: 'provider' },
|
||||
{ title: 'Type', field: 'type' },
|
||||
{ title: 'Arn', field: 'arn' },
|
||||
{ title: 'ID', field: 'id' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
title="Resources provisioned by Terraform"
|
||||
options={{ search: true, paging: false }}
|
||||
columns={columns}
|
||||
data={props.resources}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FetchTFState = () => {
|
||||
const apiRef = useApi(discoveryApiRef)
|
||||
const entity = useEntity()
|
||||
const oidcApi = useApi(keycloakOIDCAuthApiRef)
|
||||
const secretName = `tfstate-default-${entity.entity.metadata.name}`
|
||||
const { value, loading, error } = useAsync((): Promise<TFState> => {
|
||||
return getTFState(secretName, "admin", apiRef, oidcApi)
|
||||
})
|
||||
if (loading) {
|
||||
return <Progress />
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error}</Alert>;
|
||||
}
|
||||
|
||||
// const a = await getTFState("tfstate-default-helloworld", "flux-system", discoveryApi)
|
||||
// const tfdata = tfstate as TFState
|
||||
const resources = value!.resources.map(val => {
|
||||
const out: Resource = {
|
||||
name: val.name,
|
||||
provider: val.provider,
|
||||
type: val.type,
|
||||
}
|
||||
if (val.instances.length > 0) {
|
||||
out.arn = val.instances[0].attributes.arn
|
||||
out.id = val.instances[0].attributes.id
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
return <TFTable resources={resources}/>
|
||||
};
|
||||
|
||||
// horrible
|
||||
type payload = {
|
||||
kind: string
|
||||
apiVersion: string
|
||||
items?: {
|
||||
metadata: {
|
||||
labels: {
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
}[]
|
||||
metadata: {
|
||||
annotations?: {
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
type: string
|
||||
data: {
|
||||
tfstate: string
|
||||
}
|
||||
}
|
||||
|
||||
async function getTFState(name: string, namespace: string, apiRef: DiscoveryApi, oidcRef: OpenIdConnectApi): Promise<TFState> {
|
||||
const token = await oidcRef.getIdToken()
|
||||
const baseUrl = await apiRef.getBaseUrl("kubernetes")
|
||||
const proxyUrl = `${baseUrl}/proxy`
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const resp = await fetch(`${proxyUrl}/api/v1/namespaces/${namespace}/secrets/${name}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Kubernetes-Cluster': "canoe-packaging",
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (resp.ok) {
|
||||
const payload = await resp.json() as payload
|
||||
const data = Buffer.from(payload.data.tfstate, 'base64')
|
||||
let compression = "gzip"
|
||||
if ( payload.metadata.annotations && "encoding" in payload.metadata.annotations) {
|
||||
compression = payload.metadata.annotations.encoding
|
||||
}
|
||||
if (compression === "gzip") {
|
||||
const a = gunzipSync(data).toString("utf-8")
|
||||
resolve(JSON.parse(a) as TFState)
|
||||
}
|
||||
reject(`unknown compression method specified: ${compression}`)
|
||||
} else {
|
||||
if (resp.status === 404) {
|
||||
resolve( {resources: []} as TFState)
|
||||
}
|
||||
reject(`Failed to retrieve terraform information: ${resp.status}: ${resp.statusText} `)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
enum workflowStatus {
|
||||
UNKNOWN,
|
||||
DELETING = 0,
|
||||
CREATING,
|
||||
DELETED,
|
||||
NOTFOUND,
|
||||
FAILED
|
||||
}
|
||||
export const ManageBlueprint = () => {
|
||||
const entity = useEntity()
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
const oidcApi = useApi(keycloakOIDCAuthApiRef)
|
||||
|
||||
const apiRef = useApi(discoveryApiRef)
|
||||
const { value, loading, error } = useAsync((): Promise<workflowStatus> => {
|
||||
return getWorkflow(entity.entity.metadata.name, "admin", apiRef, oidcApi)
|
||||
})
|
||||
const module = entity.entity.metadata.annotations!["blueprint-module"]
|
||||
if (module === undefined) {
|
||||
return <Alert severity="error">"could not find blueprint module"</Alert>;
|
||||
}
|
||||
|
||||
const handleConfirm = async (): Promise<void> => {
|
||||
const ok = await createWorkflow(entity.entity.metadata.name, module, "admin", apiRef, oidcApi)
|
||||
if (ok) {
|
||||
handleClose()
|
||||
} else {
|
||||
console.log("oh no")
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <Progress />
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error}</Alert>;
|
||||
}
|
||||
let text: string
|
||||
switch (value) {
|
||||
case workflowStatus.DELETING:
|
||||
text = "This blueprint deployment is being deleted"
|
||||
break
|
||||
case workflowStatus.DELETED:
|
||||
text = "This blueprint deployment was successfully deleted"
|
||||
break
|
||||
case workflowStatus.NOTFOUND:
|
||||
text = "Manage this blueprint with the buttons below"
|
||||
break
|
||||
case workflowStatus.FAILED:
|
||||
return <Alert severity="error">"failed to delete blueprint deployment"</Alert>;
|
||||
default:
|
||||
return <Alert severity="error">"could not determine blueprint status"</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<InfoCard title="Blueprint management">
|
||||
<Typography color="textSecondary">
|
||||
{text}
|
||||
</Typography>
|
||||
<IconButton aria-label="delete" size="medium" onClick={handleClickOpen}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<Dialog open={open} onClose={handleClose}>
|
||||
<DialogTitle style={{ cursor: 'move' }} id="title">
|
||||
Confirmation
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<LinkButton onClick={handleClose} to="cba">
|
||||
Cancel
|
||||
</LinkButton>
|
||||
<LinkButton onClick={handleConfirm}
|
||||
to="abc" color="primary">Delete</LinkButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<IconButton aria-label="clear" size="medium">
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
<IconButton aria-label="link" size="medium">
|
||||
<LinkOffRounded />
|
||||
</IconButton>
|
||||
</InfoCard>
|
||||
)
|
||||
}
|
||||
async function getWorkflow(entityId: string, namespace: string, apiRef: DiscoveryApi, oidcRef: OpenIdConnectApi ): Promise<workflowStatus> {
|
||||
const token = await oidcRef.getIdToken()
|
||||
const baseUrl = await apiRef.getBaseUrl("kubernetes")
|
||||
const proxyUrl = `${baseUrl}/proxy`
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
labelSelector: `entity-id=${entityId},workflow-kind=delete`,
|
||||
limit: "1"
|
||||
}).toString()
|
||||
|
||||
const resp = await fetch(`${proxyUrl}/apis/argoproj.io/v1alpha1/namespaces/${namespace}/workflows?${queryParams}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Kubernetes-Cluster': "canoe-packaging",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (resp.ok) {
|
||||
const payload = await resp.json() as payload
|
||||
if (payload.items!.length > 0) {
|
||||
const labels = payload.items![0].metadata.labels
|
||||
if ("workflows.argoproj.io/phase" in labels) {
|
||||
switch (labels["workflows.argoproj.io/phase"]) {
|
||||
case "Running":
|
||||
resolve(workflowStatus.DELETING)
|
||||
break;
|
||||
case "Succeeded":
|
||||
resolve(workflowStatus.DELETED)
|
||||
break
|
||||
case "Failed":
|
||||
resolve(workflowStatus.FAILED)
|
||||
break
|
||||
default:
|
||||
reject(workflowStatus.UNKNOWN)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resolve(workflowStatus.NOTFOUND)
|
||||
}
|
||||
} else {
|
||||
reject(`Failed to retrieve terraform information: ${resp.status}: ${resp.statusText} `)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function createWorkflow(entityId: string, blueprintName: string, namespace: string, apiRef: DiscoveryApi, oidcRef: OpenIdConnectApi): Promise<Boolean> {
|
||||
const token = await oidcRef.getIdToken()
|
||||
const baseUrl = await apiRef.getBaseUrl("kubernetes")
|
||||
const proxyUrl = `${baseUrl}/proxy`
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
fieldValidation: "Strict",
|
||||
}).toString()
|
||||
const body = {
|
||||
"apiVersion": "argoproj.io/v1alpha1",
|
||||
"kind": "Workflow",
|
||||
"metadata": {
|
||||
"generateName": "blue-prints-delete-",
|
||||
"namespace": "admin",
|
||||
},
|
||||
"spec": {
|
||||
"arguments": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "blueprint-name",
|
||||
"value": `${blueprintName}`
|
||||
},
|
||||
{
|
||||
"name": "entityId",
|
||||
"value": `${entityId}`
|
||||
}
|
||||
]
|
||||
},
|
||||
"workflowTemplateRef": {
|
||||
"name": "blueprints-delete"
|
||||
}
|
||||
}
|
||||
}
|
||||
const resp = await fetch(`${proxyUrl}/apis/argoproj.io/v1alpha1/namespaces/${namespace}/workflows?${queryParams}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Kubernetes-Cluster': "canoe-packaging",
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': "application/json"
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (resp.ok) {
|
||||
resolve(true)
|
||||
} else {
|
||||
reject(`Failed to delete blueprints deployment: ${resp.status}: ${resp.statusText} `)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { BlueprintsComponent } from './ExampleComponent';
|
7700
plugins/workflows/src/components/BlueprintComponent/terraform.ts
Normal file
7700
plugins/workflows/src/components/BlueprintComponent/terraform.ts
Normal file
File diff suppressed because it is too large
Load diff
2
plugins/workflows/src/index.ts
Normal file
2
plugins/workflows/src/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
export { workflowsPlugin, EntityWorkflowsContent, keycloakOIDCAuthApiRef } from './plugin';
|
7
plugins/workflows/src/plugin.test.ts
Normal file
7
plugins/workflows/src/plugin.test.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { workflowsPlugin } from './plugin';
|
||||
|
||||
describe('workflows', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(workflowsPlugin).toBeDefined();
|
||||
});
|
||||
});
|
31
plugins/workflows/src/plugin.ts
Normal file
31
plugins/workflows/src/plugin.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import {
|
||||
ApiRef, BackstageIdentityApi, createApiRef,
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
OpenIdConnectApi,
|
||||
ProfileInfoApi, SessionApi
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { rootCatalogWorkflowsRouteRef } from './routes';
|
||||
|
||||
export const workflowsPlugin = createPlugin({
|
||||
id: 'workflows',
|
||||
routes: {
|
||||
entityContent: rootCatalogWorkflowsRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const EntityWorkflowsContent = workflowsPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'EntityWorkflowsContent',
|
||||
component: () =>
|
||||
import('./components/BlueprintComponent').then(m => m.BlueprintsComponent),
|
||||
mountPoint: rootCatalogWorkflowsRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const keycloakOIDCAuthApiRef: ApiRef<
|
||||
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
> = createApiRef({
|
||||
id: 'auth.keycloak-oidc-provider',
|
||||
});
|
5
plugins/workflows/src/routes.ts
Normal file
5
plugins/workflows/src/routes.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootCatalogWorkflowsRouteRef = createRouteRef({
|
||||
id: 'workflows',
|
||||
});
|
2
plugins/workflows/src/setupTests.ts
Normal file
2
plugins/workflows/src/setupTests.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
import '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
80
test-template.yaml
Normal file
80
test-template.yaml
Normal file
|
@ -0,0 +1,80 @@
|
|||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
kind: Template
|
||||
metadata:
|
||||
name: test-template
|
||||
title: TESTING
|
||||
description: test
|
||||
spec:
|
||||
owner: backstage/techdocs-core
|
||||
type: service
|
||||
# these are the steps which are rendered in the frontend with the form input
|
||||
parameters:
|
||||
- title: Fill in some steps
|
||||
required:
|
||||
- name
|
||||
- owner
|
||||
properties:
|
||||
name:
|
||||
title: Application Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
ui:autofocus: true
|
||||
ui:options:
|
||||
rows: 5
|
||||
owner:
|
||||
title: Owner
|
||||
type: string
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
labels:
|
||||
title: Labels
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Labels to apply to the application
|
||||
namespace:
|
||||
title: Namespace
|
||||
type: string
|
||||
description: Namespace to deploy this application into. Optional. Defaults to application name.
|
||||
ui:options:
|
||||
rows: 5
|
||||
clusterName:
|
||||
title: Cluster Name
|
||||
type: string
|
||||
default: canoe-packaging
|
||||
description: Name of the cluster to run this in
|
||||
- title: Workflow params
|
||||
properties:
|
||||
workflowParams:
|
||||
title: workflow parameters
|
||||
type: array
|
||||
description: workflow parameters
|
||||
ui:autofocus: true
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
steps:
|
||||
- id: flow
|
||||
name: Flow
|
||||
action: workflows:argo:invoke
|
||||
input:
|
||||
templateName: workflow-template-whalesay-template
|
||||
namespace: admin
|
||||
clusterName: ${{ parameters.clusterName }}
|
||||
parameters: ${{ parameters.workflowParams }}
|
||||
|
||||
# output:
|
||||
# links:
|
||||
# - title: Open in catalog
|
||||
# icon: catalog
|
||||
# entityRef: ${{ steps['register'].output.entityRef }}
|
|
@ -1,7 +1,10 @@
|
|||
{
|
||||
"extends": "@backstage/cli/config/tsconfig.json",
|
||||
"include": [
|
||||
"packages/*/src"
|
||||
"packages/*/src",
|
||||
"plugins/*/src",
|
||||
"plugins/*/dev",
|
||||
"plugins/*/migrations"
|
||||
],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
|
|
Loading…
Reference in a new issue