Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
0a4d5f7
chore(ci): Add workflow to deploy charts
nikolaik Mar 25, 2025
d7ed6a2
chore: Add codeowners
nikolaik Mar 25, 2025
209b9c6
chore: Rename to new repo
nikolaik Mar 25, 2025
4b53bf1
fix(chart): Update to new registry
nikolaik Mar 25, 2025
b43947e
chore: Add chart testing workflow
nikolaik Mar 25, 2025
b2fb585
fix(chart): Use stable version
nikolaik Mar 28, 2025
00dedf1
feat(monitoring): optional Prometheus metrics exporter
marcusramberg Apr 15, 2025
be053be
tests: add unit tests for mcache
marcusramberg Apr 23, 2025
e74fb5a
tests: add unit tests for envconfig
marcusramberg Apr 23, 2025
cfbeb63
tests: add a simple server test
marcusramberg Apr 23, 2025
cdcd4f0
tests: add tests for the auth middleware
marcusramberg Apr 23, 2025
ab2aed6
tests: Add tests for the router module
marcusramberg Apr 25, 2025
c1e309c
tests: Add tests for github module.
marcusramberg Apr 25, 2025
d0cb53f
tests: add tests for the expect module.
marcusramberg Apr 25, 2025
2b4c138
tests: add tests for the cache service
marcusramberg Apr 25, 2025
3d1ed82
chore(chart): Roll image
nikolaik May 5, 2025
b28cb8c
fix: update type enum to make it less likely to pass an invalid value…
marcusramberg May 13, 2025
7f38404
chore(chart): roll image
marcusramberg May 13, 2025
30d4db3
feat: add version support, split module and namespace
marcusramberg May 13, 2025
8148cba
fix: Order labels by name
nikolaik May 14, 2025
7cc3886
chore: Add renovate config
nikolaik May 13, 2025
a060cd6
chore(renovate): Fix matching on ghcr.io hosted image
nikolaik May 14, 2025
0b73cb9
chore(deps): update ghcr.io/remarkable/orbit docker tag to v0.1.3
renovate[bot] May 14, 2025
828dc9c
chore: Roll chart version
nikolaik May 14, 2025
bec23f8
fix: add error handling to fix all golangci-lint warnings
marcusramberg May 14, 2025
a2f6769
chore(deps): update actions/setup-go action to v5
renovate[bot] Jun 1, 2025
c1fd1b0
chore(deps): update docker/build-push-action action to v6
renovate[bot] Jun 1, 2025
ef2bf5d
chore(deps): update dependency go to 1.24
renovate[bot] May 31, 2025
1da40d8
chore(deps): update actions/checkout action to v4
renovate[bot] May 31, 2025
21fac16
docs: improve README
marcusramberg Jun 9, 2025
c89cfb6
chore(deps): update docker/metadata-action action to v5
renovate[bot] Jul 1, 2025
4459402
chore(deps): update docker/login-action action to v3
renovate[bot] Jul 1, 2025
507a2e7
chore(deps): update all non-major dependencies
renovate[bot] Aug 31, 2025
c71f506
chore(deps): update docker.io/library/golang docker tag to v1.25
renovate[bot] Aug 31, 2025
ce19a66
chore(deps): update actions/setup-python action to v6
renovate[bot] Sep 30, 2025
d42fa31
chore(deps): update actions/setup-go action to v6
renovate[bot] Oct 1, 2025
238b873
chore(deps): update actions/checkout action to v5
renovate[bot] Oct 1, 2025
49a46bc
chore(deps): update all non-major dependencies
renovate[bot] Nov 30, 2025
6ca009e
chore(deps): update actions/checkout action to v6
renovate[bot] Nov 30, 2025
f68b063
chore(deps): update all non-major dependencies
renovate[bot] Mar 31, 2026
9839ce9
chore(deps): update docker.io/library/golang docker tag to v1.26
renovate[bot] Mar 31, 2026
8f58121
chore(deps): update azure/setup-helm action to v5
renovate[bot] Mar 31, 2026
e624b60
chore(deps): update docker/build-push-action action to v7
renovate[bot] Mar 31, 2026
08272d5
chore(deps): update docker/login-action action to v4
renovate[bot] Apr 1, 2026
6e15358
chore(deps): update docker/metadata-action action to v6
renovate[bot] Apr 1, 2026
6a6eb55
chore(chart): Add emptyDir volume for cache path
nikolaik Jun 25, 2026
22203c9
fix!: Keep auth semantics when cache is enabled
nikolaik Jun 12, 2026
6db9490
fix(chart): Roll image version to v0.2.0
nikolaik Jun 25, 2026
70182b1
fix(chart): Put cache expiry value in CACHE_EXPIRATION
nikolaik Jun 25, 2026
d82f053
chore(deps): update dependency helm to v4
renovate[bot] Jul 9, 2026
00b5858
chore(deps): update actions/checkout action to v7
renovate[bot] Jun 30, 2026
ff9a41e
chore(deps): update azure/setup-helm action to v5.0.1
renovate[bot] Jul 27, 2026
9404aac
chore(deps): update actions/setup-go action to v7
renovate[bot] Jul 31, 2026
4da2732
chore(deps): update actions/setup-python action to v7
renovate[bot] Jul 31, 2026
c03de14
refactor(cache): Clean up ProxyDownload error handling
nikolaik Aug 10, 2026
533e740
fix(cache): Write files atomically and mutex cache reads
nikolaik Aug 10, 2026
caae57f
fix(chart): Roll image version to v0.2.1
nikolaik Aug 18, 2026
8149ad2
chore(deps): update docker.io/library/golang docker tag to v1.27
renovate[bot] Aug 31, 2026
3e4275a
chore(deps): update all non-major dependencies
renovate[bot] Aug 31, 2026
0eeedeb
feat: Cache paginated GitHub tags per repository
nikolaik Jun 24, 2026
f3965ba
chore: Update codeowners
nikolaik Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @reMarkable/software-cloud
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,23 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v7

- name: Log in to the Container registry
uses: docker/login-action@v2
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
uses: docker/build-push-action@v4
uses: docker/build-push-action@v7
with:
context: .
file: build/Dockerfile
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Release Chart

on:
push:
branches:
- main
paths:
- deploy/charts/**
- .github/workflows/release.yml

jobs:
release:
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"

- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.7.0
with:
charts_dir: deploy/charts
config: cr.yaml
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
49 changes: 49 additions & 0 deletions .github/workflows/test-chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Lint & Test chart

on:
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Setup Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v4.2.4

- uses: actions/setup-python@v7
with:
python-version: 3.14

- name: Setup chart-testing
uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0

- name: Run ct list-changed
id: list-changed
run: |
changed=$(ct list-changed --config ct.yaml --target-branch ${{ github.event.repository.default_branch }})
if [[ -n "$changed" ]]; then
echo "changed=true" >> $GITHUB_OUTPUT
fi

- name: Run chart-testing (lint)
if: steps.list-changed.outputs.changed == 'true'
run: ct lint --config ct.yaml

- name: Create KIND Cluster
if: steps.list-changed.outputs.changed == 'true'
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0

- name: Run chart-testing (install)
if: steps.list-changed.outputs.changed == 'true'
run: |
# Pass in a random MODULES_PROXY_SECRET secret
ct install \
--config ct.yaml \
--helm-extra-set-args "--set extraEnvs[0].name=MODULES_PROXY_SECRET,extraEnvs[0].value=$(openssl rand -base64 32)"
6 changes: 3 additions & 3 deletions .github/workflows/test.yml → .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v7

- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v7
with:
go-version: "1.22"
go-version: "1.27"

- name: Run tests
run: go test -race -cover ./...
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env*
*.pem
.terraform/
tmp/
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
# Orbit

A small proxy to turn a Github mono-repo into a Terraform module registry.
A small proxy to turn a GitHub mono-repo into a Terraform module registry. This
proxy expects releases in the same format as a golang monorepo
(`module/vX.Y.Z`), with each module having a directory on the root of the
repository. You might find [release
please](https://github.com/googleapis/release-please) useful for managing the
release process.

## Supported ENV variables

| Environment Variable | Type | Default | Required | Description |
| -------------------------- | -------- | ------- | -------- | -------------------------------------- |
| MODULES_PROXY_SECRET | []byte | | Yes | Secret key for proxy token encryption. |
| CACHE_ENABLED | bool | | No | Enable or disable caching. |
| CACHE_AUTH_DISABLED | bool | | No | Disable auth for cached requests. |
| CACHE_PATH | string | /tmp | No | Path to store cache files. |
| CACHE_EXPIRATION | duration | 10s | No | Cache expiration duration. |
| GITHUB_REPOSITORIES | map | | No | Allowed repositories (per org). |
| GITHUB_ORG_MAPPINGS | map | | No | Organization name mappings. |
| GITHUB_TOKEN | string | | No | GitHub API token. |
| MODULES_TOKEN_EXPIRATION | duration | 60s | No | Expiration time for proxy tokens. |
| SERVER_HOST | string | | No | Server listen host. |
| SERVER_PORT | int | 8080 | No | Server listen port. |
| SERVER_TIMEOUT_HANDLER | duration | 10s | No | HTTP handler timeout. |
| SERVER_TIMEOUT_IDLE | duration | | No | HTTP idle timeout. |
| SERVER_TIMEOUT_READ | duration | | No | HTTP read timeout. |
| SERVER_TIMEOUT_READ_HEADER | duration | 2s | No | HTTP read header timeout. |
| SERVER_TIMEOUT_SHUTDOWN | duration | 5s | No | Graceful shutdown timeout. |
| SERVER_TIMEOUT_WRITE | duration | | No | HTTP write timeout. |
| SERVER_TLS_ENABLED | bool | | No | Enable TLS for the server. |
| SERVER_TLS_CERT_FILE | string | | No | TLS certificate file path. |
| SERVER_TLS_KEY_FILE | string | | No | TLS key file path. |
| SERVER_METRICS_ENABLED | bool | false | No | Enable metrics endpoint. |
| SERVER_METRICS_PORT | int | 9090 | No | Metrics server port. |

**Notes:**

- Duration values (e.g., 10s, 60s) are Go duration strings (e.g., 1m, 30s).
- Prefixes like `CACHE_`, `GITHUB_`, `MODULES_`, and `SERVER_` are used for grouping related variables.
- Some variables (like maps) may require specific formatting (e.g., JSON or comma-separated values).
- MODULES_PROXY_SECRET must be a base64-encoded 16, 24 or 32-byte key for AES encryption.

## Deployment

Orbit can easily be deployed using Docker, or by just running the binary
provided in the release. If you want to deploy using Kubernetes, we're providing
a helm chart under `deploy/charts/orbit`.
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ tasks:
build:docker:
desc: Build Docker container image
cmds:
- docker build --file build/Dockerfile -t hedlund/orbit .
- docker build --file build/Dockerfile -t remarkable/orbit .

generate:cert:
desc: Generate SSL/TLS certificate for localhost
Expand Down
2 changes: 1 addition & 1 deletion build/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM docker.io/library/golang:1.22-alpine AS build
FROM docker.io/library/golang:1.27-alpine AS build
ARG entrypoint="./cmd/server"

WORKDIR /app
Expand Down
54 changes: 39 additions & 15 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@ import (
"os"
"time"

"github.com/hedlund/orbit/pkg/auth"
"github.com/hedlund/orbit/pkg/envconfig"
"github.com/hedlund/orbit/pkg/github"
"github.com/hedlund/orbit/pkg/mcache"
"github.com/hedlund/orbit/pkg/router"
"github.com/hedlund/orbit/pkg/server"
"github.com/hedlund/orbit/services/modules"
"github.com/reMarkable/orbit/pkg/auth"
"github.com/reMarkable/orbit/pkg/envconfig"
"github.com/reMarkable/orbit/pkg/github"
"github.com/reMarkable/orbit/pkg/mcache"
"github.com/reMarkable/orbit/pkg/router"
"github.com/reMarkable/orbit/pkg/server"
"github.com/reMarkable/orbit/services/modules"
)

type config struct {
Cache struct {
Enabled bool `envconfig:"ENABLED"`
Path string `envconfig:"PATH" default:"/tmp"`
Expiration time.Duration `envconfig:"EXPIRATION" default:"10s"`
Enabled bool `envconfig:"ENABLED"`
AuthDisabled bool `envconfig:"AUTH_DISABLED"`
Path string `envconfig:"PATH" default:"/tmp"`
Expiration time.Duration `envconfig:"EXPIRATION" default:"1m"`
} `envconfig:"CACHE_"`
Github github.Config `envconfig:"GITHUB_"`
Modules modules.Config `envconfig:"MODULES_"`
Expand All @@ -36,22 +37,41 @@ func main() {

log := slog.New(slog.NewTextHandler(os.Stdout, nil))

var cache modules.KeyValueStore
if cfg.Cache.Enabled {
c := mcache.New[string, []string](cfg.Cache.Expiration)
if cfg.Cache.Expiration > 0 {
// Start a cleanup loop to remove expired items from the cache periodically.
stop := mcache.StartCleanupLoop(c, cfg.Cache.Expiration)
defer stop()
}
cache = c
}

var repo modules.Repository
repo = github.New(cfg.Github, &http.Client{
Timeout: 5 * time.Second,
})
}, cache)

if cfg.Cache.Enabled {
log.Info("enabling cache", "path", cfg.Cache.Path, "expiration", cfg.Cache.Expiration)
log.Info("enabling cache", "path", cfg.Cache.Path, "expiration", cfg.Cache.Expiration, "authDisabled", cfg.Cache.AuthDisabled)
if cfg.Cache.AuthDisabled {
log.Warn("auth is disabled for cached request. This is dangerous when used with private repos, or in environments with no other access control mechanisms in place")
}
repo = modules.NewCache(
repo,
mcache.New[string, []string](cfg.Cache.Expiration),
cache,
modules.StoreInPath(cfg.Cache.Path),
log,
cfg.Cache.AuthDisabled,
)
}

h, err := modules.NewHTTP(cfg.Modules, log, repo)
mh, err := modules.NewMetricsHandler(log)
if err != nil {
panic(err)
}
h, err := modules.NewHTTP(cfg.Modules, log, repo, mh)
if err != nil {
panic(err)
}
Expand All @@ -64,12 +84,16 @@ func main() {
r.Get("/v1/modules/:namespace/:name/:system/:version/proxy", h.ProxyDownload)
r.Get("/.well-known/terraform.json", discovery)

http.DefaultServeMux.HandleFunc("GET /metrics", mh.Metrics)

if err := server.Start(cfg.Server, log, r); err != nil {
panic(err)
}
}

func discovery(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Write([]byte(`{"modules.v1":"/v1/modules"}`))
if _, err := w.Write([]byte(`{"modules.v1":"/v1/modules"}`)); err != nil {
slog.Error("failed to write response", "error", err)
}
}
3 changes: 3 additions & 0 deletions cr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
generate-release-notes: true
release-name-template: "chart-{{ .Name }}-{{ .Version }}"
mark_as_latest: false
3 changes: 3 additions & 0 deletions ct.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
chart-dirs:
- deploy/charts
validate-maintainers: false
26 changes: 5 additions & 21 deletions deploy/charts/orbit/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
apiVersion: v2
name: orbit
description: A small proxy to turn a Github mono-repo into a Terraform module registry.

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.0.1

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
# FIXME: Update version number when first version is released
appVersion: "0.0.0"
version: 0.2.2
# renovate: image=ghcr.io/reMarkable/orbit
appVersion: "v0.2.1"
maintainers:
- name: marcusramberg
2 changes: 2 additions & 0 deletions deploy/charts/orbit/ci/metrics-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
metrics:
enabled: true
2 changes: 2 additions & 0 deletions deploy/charts/orbit/ci/token-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github:
token: c3VwZXJzZWNyZXQxMjM0IQ==
30 changes: 22 additions & 8 deletions deploy/charts/orbit/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,34 +54,48 @@ spec:
value: "{{ .Values.github.repositories }}"
{{- if .Values.cache.enabled }}
- name: CACHE_ENABLED
value: true
value: "true"
- name: CACHE_PATH
value: {{ .Values.cache.path }}
- name: CACHE_EXPIRY
value: {{ .Values.cache.expiry }}
{{- end }}
value: "{{ .Values.cache.path }}"
- name: CACHE_EXPIRATION
value: "{{ .Values.cache.expiry }}"
{{- end }}
{{- if .Values.metrics.enabled }}
- name: METRICS_ENABLED
value: "true"
- name: METRICS_PORT
value: "{{ .Values.metrics.service.port }}"
{{- end }}
{{- with .Values.extraEnvs }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.github.token}}
envFrom:
- secretRef:
name: {{ include "orbit.fullname "}}
name: {{ include "orbit.fullname" .}}
{{- else if .Values.github.token_secret }}
envFrom:
- secretRef:
name: {{ .Values.github.token_secret }}
{{- if .Values.extraSecretsMounts.enabled }}
{{- if or .Values.extraSecretsMounts.enabled .Values.cache.enabled }}
volumeMounts:
{{- if .Values.cache.enabled }}
- name: cache
mountPath: {{ .Values.cache.path }}
{{- end }}
{{- range .Values.extraSecretsMounts.secrets }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
readOnly: {{ .readOnly }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.extraSecretsMounts.enabled }}
{{- if or .Values.extraSecretsMounts.enabled .Values.cache.enabled }}
volumes:
{{- if .Values.cache.enabled }}
- name: cache
emptyDir: {}
{{- end }}
{{- range .Values.extraSecretsMounts.secrets }}
{{- if .secretName }}
- name: {{ .name }}
Expand Down
Loading