Skip to content

Commit

Permalink
feat: Add project files and source (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
meyfa authored Jul 6, 2024
1 parent 5fcaf18 commit f6f51a3
Show file tree
Hide file tree
Showing 87 changed files with 9,506 additions and 1 deletion.
19 changes: 19 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Default ignored files
.env
.idea
node_modules/
renovate.json
.github
README.md
.gitignore
.gitattributes

# build folders and files
/dist
.nyc_output
/tmp
/coverage
/junit.xml

# configuration
/config
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
* text=auto
.env text eol=lf
*.sh text eol=lf
*.bat text eol=crlf
*.conf text eol=lf
30 changes: 30 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Build

on:
push:
branches:
- main
pull_request:

permissions:
contents: read

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build
id: docker-build
uses: docker/build-push-action@v6
with:
context: ./
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Lint

on:
push:
branches:
- main
pull_request:

permissions:
contents: read

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run build
- run: npm run lint
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.idea
.fleet
node_modules/
.DS_Store
npm-debug.log*
.npmrc
.env

# build folders and files
.nyc_output
/tmp
/coverage
/junit.xml
/build
/dist
/backend/dist
/frontend/dist

# configuration files
config/*.yaml
40 changes: 40 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -- compilation --
FROM node:20.15.0-alpine AS build
WORKDIR /app

# install dependencies
COPY package*.json ./
COPY backend/package*.json ./backend/
COPY frontend/package*.json ./frontend/
RUN npm ci

# copy in app code and build it
COPY . .
RUN npm run build


# -- execution --
FROM node:20.15.0-alpine
WORKDIR /app

# install PRODUCTION dependencies
COPY package*.json ./
COPY backend/package*.json ./backend/
COPY frontend/package*.json ./frontend/
RUN npm ci --omit=dev
RUN apk add --no-cache tini

# add the already compiled code and the default config
# (custom config must be set via volume)
COPY --from=build /app/dist ./dist
COPY --from=build /app/backend/dist ./backend/dist
COPY --from=build /app/frontend/dist ./frontend/dist

# config
USER node
ENV PORT=8080
EXPOSE 8080

# use tini as init process since Node.js isn't designed to be run as PID 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "--disable-proto=delete", "dist/server.js"]
128 changes: 127 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,127 @@
# foreman
# foreman

A Kubernetes application for managing Renovate jobs, developed by [Contane](https://contane.net).

Foreman is intended to be deployed together with self-hosted [Mend Renovate](https://www.mend.io/renovate/), either in
the same or a secondary Kubernetes cluster.
It provides a user interface for monitoring the Renovate CronJob including interactive progress and logs as well as the
ability to trigger custom jobs, speeding up the dependency upgrade cycle in the case of many repositories.

## Instructions

To build and run:

```sh
$ npm run build
$ npm start
```

Note that you will need to authenticate on the web interface.
During development, it is recommended to set a password for local login. See the configuration section below.

## Configuration

Create a YAML configuration file inside the `config` directory. The file can have any name ending with `.yaml`.
If there are multiple configuration files, they will be merged in alphabetical order. For example, if you have
`config/00-config.yaml` and `config/01-config.yaml`, the settings in `config/01-config.yaml` will take precedence.
The following sections describe the available configuration options.

Environment variables can be set on the command line or in an `.env` file in the root directory of the project.

### KubeConfig

Foreman supports multiple KubeConfig sources. The source to use can be specified by setting the `kubeConfig.source`
to one of the following values:

- `in-cluster`: Use the KubeConfig from the Kubernetes cluster environment. This is the default.
- `file`: Use the KubeConfig from the file specified by the `KUBECONFIG` environment variable, or `~/.kube/config`.

In the `in-cluster` mode, Foreman will look at the `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment
variables to determine the location of the Kubernetes API server. These variables are automatically set by Kubernetes.

You can use the `file` mode when developing Foreman locally, or when deploying it outside of Kubernetes.
If necessary, override the `KUBECONFIG` environment variable to point to the correct file.

You can select a context by setting `kubeConfig.context`. This defaults to the KubeConfig file's current context.

### CronJob Selection

Foreman queries a defined CronJob resource to determine which Renovate jobs to manage. By default, this is
`renovate/renovate` (namespace/name). You can override this by setting `cronJob.namespace` and `cronJob.name`.

### Session Cookies

Foreman authentication is stateless. Client auth information is stored in a cookie, which is encrypted using a key.
The key is generated on startup and is stored in memory. This means that the key will be different on each startup,
and that all clients will be logged out when Foreman is restarted. Note that key generation consumes about 256 MiB of
memory.

To avoid this, you can set `cookies.key` to a fixed value. This will cause the same key to be used on each startup,
and will allow clients to stay logged in across restarts, and less memory to be used.

`cookies.key` must be base64-encoded. To generate a key on Linux, run:

```sh
$ npx --yes @fastify/secure-session | base64
```

Sessions are valid for 24 hours by default. You can change this by setting `cookies.maxAge` to a duration string
such as "6h" or "14 days".

### Local Authentication

Foreman supports local authentication, which allows users to log in using a username and password. This is disabled by
default. To enable it, set the following config options:

```yaml
auth:
local:
enabled: true
username: "any username (default: admin)"
password: "any password"
```
### OIDC Authentication
Foreman supports authentication via OpenID Connect. To enable it, set the following config options:
```yaml
auth:
oidc:
enabled: true
issuer: "https://oidc.example.com"
clientId: "client-id"
clientSecret: "client-secret"
publicUrl: "https://foreman.example.com"
```
The following configuration must be set in the OIDC provider, assuming Foreman is running at
`https://foreman.example.com`:

- Response Type: Code
- Grant Type: Authorization Code
- Authentication Method: Basic
- Auth Token Type: Bearer
- Redirect URL: `https://foreman.example.com/api/auth/oidc/callback`

It is assumed that your OIDC provider restricts which users can log in to Foreman, as no additional role checks are
performed by Foreman.

### GitLab integration

Some GitLab-related functionality can be enabled by setting the following configuration options:

```yaml
gitlab:
host: "https://gitlab.example.com"
```

This will enable the following features:

* Linking to GitLab repositories in the Renovate job list.

### Other settings

The following additional environment variables can be set:

- `PORT`: The port to listen on. Defaults to `8080`.
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
/dist
29 changes: 29 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "backend",
"version": "0.0.0",
"type": "module",
"private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./routes": {
"types": "./dist/routes.d.ts",
"import": "./dist/routes.js"
}
},
"scripts": {
"build": "node -e \"fs.rmSync('./dist',{force:true,recursive:true})\" && tsc",
"lint": "tsc --noEmit -p tsconfig.lint.json"
},
"dependencies": {
"pino-pretty": "11.2.1",
"secure-json-parse": "2.7.0"
}
}
6 changes: 6 additions & 0 deletions backend/src/annotations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum ForemanAnnotations {
Manual = 'foreman.contane.net/manual',
TriggeredBy = 'foreman.contane.net/triggered-by',
RepositoryScope = 'foreman.contane.net/repository-scope',
DebugLogging = 'foreman.contane.net/debug-logging'
}
13 changes: 13 additions & 0 deletions backend/src/api/auth/local-login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { FastifyPluginAsync } from 'fastify'
import { authenticateLocal, LocalStrategyRequestBody } from '../../auth/local-strategy.js'

export interface LocalLoginRoute {
Body: LocalStrategyRequestBody
Reply: {}
}

export const localLoginAuthRoute = (): FastifyPluginAsync => async (app) => {
app.post<LocalLoginRoute>('/', { preValidation: authenticateLocal() }, () => {
return {}
})
}
16 changes: 16 additions & 0 deletions backend/src/api/auth/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FastifyPluginAsync } from 'fastify'
import { authenticateSession } from '../../auth/common.js'

export interface LogoutRoute {
Body: {}
Reply: {}
}

export const logoutAuthRoute = (): FastifyPluginAsync => async (app) => {
app.addHook('preValidation', authenticateSession())

app.post<LogoutRoute>('/', async (request, reply) => {
await request.logout()
return {}
})
}
24 changes: 24 additions & 0 deletions backend/src/api/auth/me.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FastifyPluginAsync } from 'fastify'
import { forbidden } from '../errors.js'
import { authenticateSession } from '../../auth/common.js'

export interface MeRoute {
Reply: {
username: string
strategy: string
}
}

export const meAuthRoute = (): FastifyPluginAsync => async (app) => {
app.addHook('preValidation', authenticateSession())

app.get<MeRoute>('/', async (request, reply) => {
if (request.user == null) {
return await forbidden(reply)
}
return {
username: request.user.username,
strategy: request.user.strategy
}
})
}
12 changes: 12 additions & 0 deletions backend/src/api/auth/oidc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FastifyPluginAsync } from 'fastify'
import { authenticateOidc } from '../../auth/oidc-strategy.js'
import { AuthStrategy } from '../../auth/common.js'

export const oidcAuthRoute = (): FastifyPluginAsync => async (app) => {
app.get('/', authenticateOidc())

app.get('/callback', authenticateOidc({
successRedirect: '/',
failureRedirect: '/login?login_error=' + AuthStrategy.OIDC
}))
}
14 changes: 14 additions & 0 deletions backend/src/api/auth/strategies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FastifyPluginAsync } from 'fastify'
import { AuthStrategy } from '../../auth/common.js'

export interface StrategiesRoute {
Reply: string[]
}

export const strategiesAuthRoute = (strategies: AuthStrategy[]): FastifyPluginAsync => async (app) => {
// route is not authenticated - provides a list of available strategies to log in with

app.get<StrategiesRoute>('/', async () => {
return strategies
})
}
Loading

0 comments on commit f6f51a3

Please sign in to comment.