Skip to content

Commit

Permalink
Merge branch 'main' into release/v0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriel-samfira committed Jul 25, 2023
2 parents a0a5611 + 7d74b6b commit d2fb5e3
Show file tree
Hide file tree
Showing 215 changed files with 16,335 additions and 7,353 deletions.
49 changes: 49 additions & 0 deletions .github/workflows/build-and-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: "Build GARM images"
on:
workflow_dispatch:
inputs:
push_to_project:
description: "Project to build images for"
required: true
default: "ghcr.io/cloudbase"
ref:
description: "Ref to build"
required: true
default: "main"

permissions:
contents: read

jobs:
images:
permissions:
packages: write
name: "Build GARM images"
runs-on: ubuntu-22.04
steps:
- name: "Checkout"
uses: actions/checkout@v3
with:
path: src/github.com/cloudbase/garm
fetch-depth: 0

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

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
run: |
cd src/github.com/cloudbase/garm
VERSION=$(git describe --tags --match='v[0-9]*' --always ${{ github.event.inputs.ref }})
docker buildx build \
--provenance=false \
--platform linux/amd64,linux/arm64 \
--build-arg="GARM_REF=${{ github.event.inputs.ref }}" \
-t ${{ github.event.inputs.push_to_project }}/garm:"${VERSION}" \
--push .
32 changes: 25 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
FROM docker.io/golang:alpine
FROM docker.io/golang:alpine AS builder
ARG GARM_REF

WORKDIR /root
USER root
LABEL stage=builder

RUN apk add musl-dev gcc libtool m4 autoconf g++ make libblkid util-linux-dev git linux-headers mingw-w64-gcc
RUN apk add musl-dev gcc libtool m4 autoconf g++ make libblkid util-linux-dev git linux-headers
RUN git config --global --add safe.directory /build

ADD ./scripts/build-static.sh /build-static.sh
RUN chmod +x /build-static.sh
ADD . /build/garm
RUN cd /build/garm && git checkout ${GARM_REF}
RUN git clone https://github.com/cloudbase/garm-provider-azure /build/garm-provider-azure
RUN git clone https://github.com/cloudbase/garm-provider-openstack /build/garm-provider-openstack

CMD ["/bin/sh"]
RUN cd /build/garm && go build -o /bin/garm \
-tags osusergo,netgo,sqlite_omit_load_extension \
-ldflags "-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" \
/build/garm/cmd/garm
RUN mkdir -p /opt/garm/providers.d
RUN cd /build/garm-provider-azure && go build -ldflags="-linkmode external -extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-azure .
RUN cd /build/garm-provider-openstack && go build -ldflags="-linkmode external -extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-openstack .

FROM scratch

COPY --from=builder /bin/garm /bin/garm
COPY --from=builder /opt/garm/providers.d/garm-provider-openstack /opt/garm/providers.d/garm-provider-openstack
COPY --from=builder /opt/garm/providers.d/garm-provider-azure /opt/garm/providers.d/garm-provider-azure
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

ENTRYPOINT ["/bin/garm", "-config", "/etc/garm/config.toml"]
15 changes: 15 additions & 0 deletions Dockerfile.build-static
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM docker.io/golang:alpine

WORKDIR /root
USER root

RUN apk add musl-dev gcc libtool m4 autoconf g++ make libblkid util-linux-dev git linux-headers mingw-w64-gcc

RUN wget http://musl.cc/aarch64-linux-musl-cross.tgz -O /tmp/aarch64-linux-musl-cross.tgz && \
tar --strip-components=1 -C /usr/local -xzf /tmp/aarch64-linux-musl-cross.tgz && \
rm /tmp/aarch64-linux-musl-cross.tgz

ADD ./scripts/build-static.sh /build-static.sh
RUN chmod +x /build-static.sh

CMD ["/bin/sh"]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ default: build
.PHONY : build-static test install-lint-deps lint go-test fmt fmtcheck verify-vendor verify
build-static:
@echo Building garm
docker build --tag $(IMAGE_TAG) .
docker build --tag $(IMAGE_TAG) -f Dockerfile.build-static .
docker run --rm -e USER_ID=$(USER_ID) -e USER_GROUP=$(USER_GROUP) -v $(PWD):/build/garm:z $(IMAGE_TAG) /build-static.sh
@echo Binaries are available in $(PWD)/bin

Expand Down
127 changes: 21 additions & 106 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# GitHub Actions Runner Manager (garm)
# GitHub Actions Runner Manager (GARM)

[![Go Tests](https://github.com/cloudbase/garm/actions/workflows/go-tests.yml/badge.svg)](https://github.com/cloudbase/garm/actions/workflows/go-tests.yml)

Welcome to garm!
Welcome to GARM!

Garm enables you to create and automatically maintain pools of [self-hosted GitHub runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners), with autoscaling that can be used inside your github workflow runs.

The goal of ```garm``` is to be simple to set up, simple to configure and simple to use. It is a single binary that can run on any GNU/Linux machine without any other requirements other than the providers it creates the runners in. It is intended to be easy to deploy in any environment and can create runners in any system you can write a provider for. There is no complicated setup process and no extremely complex concepts to understand. Once set up, it's meant to stay out of your way.
The goal of ```GARM``` is to be simple to set up, simple to configure and simple to use. It is a single binary that can run on any GNU/Linux machine without any other requirements other than the providers it creates the runners in. It is intended to be easy to deploy in any environment and can create runners in any system you can write a provider for. There is no complicated setup process and no extremely complex concepts to understand. Once set up, it's meant to stay out of your way.

Garm supports creating pools on either GitHub itself or on your own deployment of [GitHub Enterprise Server](https://docs.github.com/en/[email protected]/admin/overview/about-github-enterprise-server). For instructions on how to use ```garm``` with GHE, see the [credentials](/doc/github_credentials.md) section of the documentation.
Garm supports creating pools on either GitHub itself or on your own deployment of [GitHub Enterprise Server](https://docs.github.com/en/[email protected]/admin/overview/about-github-enterprise-server). For instructions on how to use ```GARM``` with GHE, see the [credentials](/doc/github_credentials.md) section of the documentation.

## Join us on slack

Expand All @@ -18,121 +18,36 @@ Whether you're running into issues or just want to drop by and say "hi", feel fr

## Installing

## Build from source
Check out the [quickstart](/doc/quickstart.md) document for instructions on how to install ```GARM```. If you'd like to build from source, check out the [building from source](/doc/building_from_source.md) document.

You need to have Go installed, then run:
## Installing external providers

```bash
git clone https://github.com/cloudbase/garm
cd garm
go install ./...
```
External providers are binaries that GARM calls into to create runners in a particular IaaS. There are currently two external providers available:

You should now have both ```garm``` and ```garm-cli``` in your ```$GOPATH/bin``` folder.
* [OpenStack](https://github.com/cloudbase/garm-provider-openstack)
* [Azure](https://github.com/cloudbase/garm-provider-azure)

If you have docker/podman installed, you can also build statically linked binaries by running:

```bash
make build-static
```

The ```garm``` and ```garm-cli``` binaries will be built and copied to the ```bin/``` folder in your current working directory.

## Install the service

Add a new system user:

```bash
useradd --shell /usr/bin/false \
--system \
--groups lxd \
--no-create-home garm
```

The ```lxd``` group is only needed if you have a local LXD install and want to connect to the unix socket to use it. If you're connecting to a remote LXD server over TCP, you can skip adding the ```garm``` user to the ```lxd``` group.
Copy the binary to somewhere in the system ```$PATH```:
```bash
sudo cp $(go env GOPATH)/bin/garm /usr/local/bin/garm
```
Or if you built garm using ```make```:
```bash
sudo cp ./bin/garm /usr/local/bin/garm
```
Create the config folder:
```bash
sudo mkdir -p /etc/garm
```
Copy the config template:
```bash
sudo cp ./testdata/config.toml /etc/garm/
```
Copy the external provider (optional):
```bash
sudo cp -a ./contrib/providers.d /etc/garm/
```
Copy the systemd service file:
```bash
sudo cp ./contrib/garm.service /etc/systemd/system/
```
Change permissions on config folder:
```bash
sudo chown -R garm:garm /etc/garm
sudo chmod 750 -R /etc/garm
```
Enable the service:
```bash
sudo systemctl enable garm
```
Customize the config in ```/etc/garm/config.toml```, and start the service:
```bash
sudo systemctl start garm
```
Follow the instructions in the README of each provider to install them.

## Configuration

The ```garm``` configuration is a simple ```toml```. A sample of the config file can be found in [the testdata folder](/testdata/config.toml).
There are 3 major sections of the config that require your attention:
* [Github credentials section](/doc/github_credentials.md)
* [Providers section](/doc/providers.md)
* [The database section](/doc/database.md)
Once you've configured your database, providers and github credentials, you'll need to configure your [webhooks and the callback_url](/doc/webhooks_and_callbacks.md).
At this point, you should be done. Have a look at the [running garm document](/doc/running_garm.md) for usage instructions and available features.
If you would like to use ```garm``` with a different IaaS than the ones already available, have a look at the [writing an external provider](/doc/external_provider.md) page.
If you like to optimize the startup time of new instance, take a look at the [performance considerations](/doc/performance_considerations.md) page.
The ```GARM``` configuration is a simple ```toml```. The sample config file in [the testdata folder](/testdata/config.toml) is fairly well commented and should be enough to get you started. The configuration file is split into several sections, each of which is documented in its own page. The sections are:

## Security considerations
* [The default section](/doc/config_default.md)
* [Database](/doc/database.md)
* [Github credentials](/doc/github_credentials.md)
* [Providers](/doc/providers.md)
* [Metrics](/doc/config_metrics.md)
* [JWT authentication](/doc/config_jwt_auth.md)
* [API server](/doc/config_api_server.md)

Garm does not apply any ACLs of any kind to the instances it creates. That task remains in the responsibility of the user. [Here is a guide for creating ACLs in LXD](https://linuxcontainers.org/lxd/docs/master/howto/network_acls/). You can of course use ```iptables``` or ```nftables``` to create any rules you wish. I recommend you create a separate isolated lxd bridge for runners, and secure it using ACLs/iptables/nftables.
## Optimizing your runners

You must make sure that the code that runs as part of the workflows is trusted, and if that cannot be done, you must make sure that any malicious code that will be pulled in by the actions and run as part of a workload, is as contained as possible. There is a nice article about [securing your workflow runs here](https://blog.gitguardian.com/github-actions-security-cheat-sheet/).
If you would like to optimize the startup time of new instance, take a look at the [performance considerations](/doc/performance_considerations.md) page.

## Write your own provider

The providers are interfaces between ```garm``` and a particular IaaS in which we spin up GitHub Runners. These providers can be either **native** or **external**. The **native** providers are written in ```Go```, and must implement [the interface defined here](https://github.com/cloudbase/garm/blob/main/runner/common/provider.go#L22-L39). **External** providers can be written in any language, as they are in the form of an external executable that ```garm``` calls into.
The providers are interfaces between ```GARM``` and a particular IaaS in which we spin up GitHub Runners. These providers can be either **native** or **external**. The **native** providers are written in ```Go```, and must implement [the interface defined here](https://github.com/cloudbase/garm/blob/main/runner/common/provider.go#L22-L39). **External** providers can be written in any language, as they are in the form of an external executable that ```GARM``` calls into.

There is currently one **native** provider for [LXD](https://linuxcontainers.org/lxd/) and two **external** providers for [Openstack and Azure](/contrib/providers.d/).

Expand Down
61 changes: 59 additions & 2 deletions apiserver/controllers/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import (
"net/http"
"strings"

gErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm-provider-common/util"
"github.com/cloudbase/garm/apiserver/params"
"github.com/cloudbase/garm/auth"
gErrors "github.com/cloudbase/garm/errors"
"github.com/cloudbase/garm/metrics"
runnerParams "github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner"
"github.com/cloudbase/garm/util"
wsWriter "github.com/cloudbase/garm/websocket"

"github.com/gorilla/websocket"
Expand Down Expand Up @@ -202,6 +202,13 @@ func (a *APIController) NotFoundHandler(w http.ResponseWriter, r *http.Request)
}
}

// swagger:route GET /metrics-token metrics-token GetMetricsToken
//
// Returns a JWT token that can be used to access the metrics endpoint.
//
// Responses:
// 200: JWTResponse
// 401: APIErrorResponse
func (a *APIController) MetricsTokenHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

Expand All @@ -222,6 +229,21 @@ func (a *APIController) MetricsTokenHandler(w http.ResponseWriter, r *http.Reque
}
}

// swagger:route POST /auth/login login Login
//
// Logs in a user and returns a JWT token.
//
// Parameters:
// + name: Body
// description: Login information.
// type: PasswordLoginParams
// in: body
// required: true
//
// Responses:
// 200: JWTResponse
// 400: APIErrorResponse
//
// LoginHandler returns a jwt token
func (a *APIController) LoginHandler(w http.ResponseWriter, r *http.Request) {
var loginInfo runnerParams.PasswordLoginParams
Expand Down Expand Up @@ -253,6 +275,20 @@ func (a *APIController) LoginHandler(w http.ResponseWriter, r *http.Request) {
}
}

// swagger:route POST /first-run first-run FirstRun
//
// Initialize the first run of the controller.
//
// Parameters:
// + name: Body
// description: Create a new user.
// type: NewUserParams
// in: body
// required: true
//
// Responses:
// 200: User
// 400: APIErrorResponse
func (a *APIController) FirstRunHandler(w http.ResponseWriter, r *http.Request) {
if a.auth.IsInitialized() {
err := gErrors.NewConflictError("already initialized")
Expand All @@ -279,6 +315,13 @@ func (a *APIController) FirstRunHandler(w http.ResponseWriter, r *http.Request)
}
}

// swagger:route GET /credentials credentials ListCredentials
//
// List all credentials.
//
// Responses:
// 200: Credentials
// 400: APIErrorResponse
func (a *APIController) ListCredentials(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
creds, err := a.r.ListCredentials(ctx)
Expand All @@ -293,6 +336,13 @@ func (a *APIController) ListCredentials(w http.ResponseWriter, r *http.Request)
}
}

// swagger:route GET /providers providers ListProviders
//
// List all providers.
//
// Responses:
// 200: Providers
// 400: APIErrorResponse
func (a *APIController) ListProviders(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
providers, err := a.r.ListProviders(ctx)
Expand All @@ -307,6 +357,13 @@ func (a *APIController) ListProviders(w http.ResponseWriter, r *http.Request) {
}
}

// swagger:route GET /jobs jobs ListJobs
//
// List all jobs.
//
// Responses:
// 200: Jobs
// 400: APIErrorResponse
func (a *APIController) ListAllJobs(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
jobs, err := a.r.ListAllJobs(ctx)
Expand Down
Loading

0 comments on commit d2fb5e3

Please sign in to comment.