Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
playwright.config.ts
dist
dist-types
node_modules
7 changes: 5 additions & 2 deletions .github/workflows/build-and-push.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#
name: Create and publish a Docker image

# Configures this workflow to run every time a change is pushed.
on: push
# Configures this workflow to run on pushes to main and version tags.
on:
push:
branches: [main]
tags: ['v*']

# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
Expand Down
24 changes: 17 additions & 7 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@ on:

jobs:
build:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- uses: actions/setup-node@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 18
- name: Run tests
run: |
yarn install --frozen-lockfile --network-timeout 600000
yarn tsc
node-version: 22
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile --network-timeout 600000

- name: Type check
run: yarn tsc

- name: Lint
run: yarn lint:all

- name: Build backend
run: yarn build:backend
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ site

# E2E test reports
e2e-test-report/
tsconfig.tsbuildinfo
35 changes: 9 additions & 26 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
# Stage 1 - Create yarn install skeleton layer
FROM node:18-bookworm-slim AS packages
FROM node:22-bookworm-slim 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:18-bookworm-slim AS build
FROM node:22-bookworm-slim AS build

USER node
WORKDIR /app
Expand All @@ -26,30 +24,25 @@ 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:18-bookworm-slim
FROM node:22-bookworm-slim

# 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 cnoe-cli
# Add mkdocs for the TechDocs plugin.
# Add kubectl for the kube apply plugin
RUN curl -L -o /usr/local/bin/kubectl https://dl.k8s.io/release/v1.29.9/bin/linux/amd64/kubectl && chmod +x /usr/local/bin/kubectl

# Add cnoe-cli
RUN curl -L -O https://github.com/cnoe-io/cnoe-cli/releases/download/v0.1.0/cnoe_Linux_x86_64.tar.gz && \
curl -L -O https://github.com/cnoe-io/cnoe-cli/releases/download/v0.1.0/checksums.txt && \
sha256sum -c --strict --status --ignore-missing checksums.txt && \
Expand All @@ -60,34 +53,24 @@ RUN curl -L -O https://github.com/cnoe-io/cnoe-cli/releases/download/v0.1.0/cnoe
COPY ./cnoe-wrapper.sh /usr/bin/cnoe
RUN chmod +x /usr/bin/cnoe

# Add mkdocs for TechDocs
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'

# 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 --frozen-lockfile --production --network-timeout 600000

# Copy the built packages from the build stage
COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./
COPY --chown=node:node app-config.yaml app-config.production.yaml ./

# 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
ENV NODE_OPTIONS="--no-node-snapshot"

CMD ["node", "packages/backend", "--config", "app-config.yaml"]
CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]
198 changes: 198 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
version: '3'

vars:
IMAGE_NAME: ghcr.io/cnoe-io/backstage-app
IMAGE_TAG: local
KIND_CLUSTER: localdev
K8S_CONTEXT: kind-{{.KIND_CLUSTER}}
BACKSTAGE_NS: backstage

tasks:
# ===== Development =====

dev:
desc: Start local dev server (frontend + backend)
cmds:
- yarn dev

dev:mock:
desc: Start local dev server with mock backends (no cluster needed)
cmds:
- MOCK_MODE=true yarn dev

tsc:
desc: Run TypeScript type checking
cmds:
- yarn tsc

lint:
desc: Run linter on all packages
cmds:
- yarn lint:all

test:
desc: Run all tests
cmds:
- yarn test:all

build:
desc: Build backend bundle
cmds:
- yarn build:backend

build:all:
desc: Build all packages (frontend + backend)
cmds:
- yarn build:all

clean:
desc: Clean all build artifacts
cmds:
- yarn clean

check:
desc: Run tsc + lint + build (same as PR CI)
cmds:
- task: tsc
- task: lint
- task: build

# ===== Docker =====

docker:build:
desc: Build Docker image
cmds:
- docker build -t {{.IMAGE_NAME}}:{{.IMAGE_TAG}} .

docker:run:
desc: Run Docker image locally (dev config, SQLite)
cmds:
- docker run -p 7007:7007 {{.IMAGE_NAME}}:{{.IMAGE_TAG}} node packages/backend --config app-config.yaml

# ===== idpbuilder Local Stack =====

stack:up:
desc: Bring up full local stack (ref-implementation + Kro + Backstage)
cmds:
- task: stack:idp
- task: stack:kro
- task: stack:patch
- task: stack:deploy

stack:idp:
desc: Start idpbuilder ref-implementation
cmds:
- |
idpbuilder create --use-path-routing \
--package https://github.com/cnoe-io/stacks//ref-implementation
- echo "Waiting for cluster to stabilize..."
- kubectl config use-context {{.K8S_CONTEXT}}
- kubectl wait --for=condition=Available deployment/backstage -n {{.BACKSTAGE_NS}} --timeout=600s

stack:kro:
desc: Install Kro operator and sample ResourceGraphDefinition
cmds:
- kubectl config use-context {{.K8S_CONTEXT}}
- helm install kro oci://registry.k8s.io/kro/charts/kro --namespace kro-system --create-namespace
- kubectl wait --for=condition=Established crd/resourcegraphdefinitions.kro.run --timeout=60s
- kubectl apply -f examples/local-stack/kro/manifests/install.yaml

stack:patch:
desc: Apply Backstage RBAC and config patches for Kro
cmds:
- kubectl config use-context {{.K8S_CONTEXT}}
- kubectl apply -f examples/local-stack/backstage-patch/manifests/install.yaml

stack:deploy:
desc: Build, load, and deploy local Backstage image to the cluster
cmds:
- task: docker:build
- task: stack:load
- task: stack:swap

stack:load:
desc: Load local Docker image into kind cluster
cmds:
- |
docker save {{.IMAGE_NAME}}:{{.IMAGE_TAG}} | \
docker exec -i {{.KIND_CLUSTER}}-control-plane \
ctr -n k8s.io images import --all-platforms -

stack:swap:
desc: Swap Backstage deployment to use local image
cmds:
- kubectl config use-context {{.K8S_CONTEXT}}
- kubectl -n {{.BACKSTAGE_NS}} set image deployment/backstage backstage={{.IMAGE_NAME}}:{{.IMAGE_TAG}}
- |
kubectl -n {{.BACKSTAGE_NS}} patch deployment backstage \
-p '{"spec":{"template":{"spec":{"containers":[{"name":"backstage","imagePullPolicy":"Never"}]}}}}'
- kubectl -n argocd patch application backstage --type merge -p '{"spec":{"syncPolicy":null}}'
- kubectl -n {{.BACKSTAGE_NS}} rollout status deployment/backstage --timeout=90s

stack:iterate:
desc: Rebuild and redeploy after code changes (fast cycle)
cmds:
- task: docker:build
- task: stack:load
- kubectl config use-context {{.K8S_CONTEXT}}
- kubectl -n {{.BACKSTAGE_NS}} rollout restart deployment/backstage
- kubectl -n {{.BACKSTAGE_NS}} rollout status deployment/backstage --timeout=90s

stack:logs:
desc: Tail Backstage logs
cmds:
- kubectl config use-context {{.K8S_CONTEXT}}
- kubectl logs -f deployment/backstage -n {{.BACKSTAGE_NS}}

stack:status:
desc: Show status of all stack components
cmds:
- kubectl config use-context {{.K8S_CONTEXT}}
- echo "=== Backstage ==="
- kubectl get pods -n {{.BACKSTAGE_NS}}
- echo ""
- echo "=== Kro ==="
- kubectl get pods -n kro-system
- echo ""
- echo "=== ResourceGraphDefinitions ==="
- kubectl get rgd -A
- echo ""
- echo "=== ArgoCD Applications ==="
- kubectl get applications -n argocd

stack:secrets:
desc: Show idpbuilder credentials
cmds:
- idpbuilder get secrets

stack:port-forward:
desc: Start port-forward to Backstage (localhost:7007)
cmds:
- kubectl config use-context {{.K8S_CONTEXT}}
- echo "Backstage available at http://localhost:7007"
- kubectl -n {{.BACKSTAGE_NS}} port-forward svc/backstage 7007:7007

stack:guest-auth:
desc: Enable guest auth on the cluster (for testing without Keycloak)
cmds:
- kubectl config use-context {{.K8S_CONTEXT}}
- |
kubectl -n {{.BACKSTAGE_NS}} get configmap backstage-config -o yaml | \
sed 's/keycloak-oidc:/guest:\n dangerouslyAllowOutsideDevelopment: true\n keycloak-oidc:/' | \
kubectl apply -f -
- kubectl -n {{.BACKSTAGE_NS}} rollout restart deployment/backstage

stack:down:
desc: Delete the idpbuilder cluster
cmds:
- idpbuilder delete --name {{.KIND_CLUSTER}}

# ===== CI Helpers =====

ci:pr:
desc: Run the same checks as the PR workflow
cmds:
- yarn install --frozen-lockfile --network-timeout 600000
- task: tsc
- task: lint
- task: build
19 changes: 0 additions & 19 deletions app-config.production.yaml
Original file line number Diff line number Diff line change
@@ -1,35 +1,16 @@
app:
# Should be the same as backend.baseUrl when using the `app-backend` plugin.
baseUrl: http://localhost:7007

backend:
# Note that the baseUrl should be the URL that the browser and other clients
# should use when communicating with the backend, i.e. it needs to be
# reachable not just from within the backend host, but from all of your
# callers. When its value is "http://localhost:7007", it's strictly private
# and can't be reached by others.
baseUrl: http://localhost:7007
# The listener can also be expressed as a single <host>:<port> string. In this case we bind to
# all interfaces, the most permissive setting. The right value depends on your specific deployment.
listen: ':7007'

# config options: https://node-postgres.com/api/client
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
# https://node-postgres.com/features/ssl
# you can set the sslmode configuration option via the `PGSSLMODE` environment variable
# see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require)
# ssl:
# ca: # if you have a CA file and want to verify it you can uncomment this section
# $file: <file-path>/ca/server.crt

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
# on how to get entities into the catalog.
locations: []
Loading