Skip to content

Commit 50f691e

Browse files
Merge remote-tracking branch 'upstream/main' into release/v0.1
2 parents d2fb5e3 + 8fe4f17 commit 50f691e

File tree

1,929 files changed

+72520
-155875
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,929 files changed

+72520
-155875
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: Integration Tests
2+
on:
3+
workflow_dispatch: {}
4+
schedule:
5+
- cron: "0 0 * * *"
6+
7+
jobs:
8+
integration-tests:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Checkout
12+
uses: actions/checkout@v3
13+
14+
- name: Setup Golang
15+
uses: actions/setup-go@v3
16+
with:
17+
go-version-file: go.mod
18+
19+
- name: Setup LXD
20+
uses: canonical/[email protected]
21+
22+
- name: Build GARM
23+
run: make build
24+
25+
- name: Set up ngrok
26+
id: ngrok
27+
uses: gabriel-samfira/[email protected]
28+
with:
29+
ngrok_authtoken: ${{ secrets.NGROK_AUTH_TOKEN }}
30+
port: 9997
31+
tunnel_type: http
32+
33+
- name: Setup GARM
34+
run: ./test/integration/scripts/setup-garm.sh
35+
env:
36+
GH_OAUTH_TOKEN: ${{ secrets.GH_OAUTH_TOKEN }}
37+
CREDENTIALS_NAME: test-garm-creds
38+
GARM_BASE_URL: ${{ steps.ngrok.outputs.tunnel-url }}
39+
40+
- name: Generate secrets
41+
run: |
42+
sudo apt-get -qq update && sudo apt-get -qq install -y apg
43+
44+
GARM_PASSWORD=$(apg -n1 -m32)
45+
REPO_WEBHOOK_SECRET=$(apg -n1 -m32)
46+
ORG_WEBHOOK_SECRET=$(apg -n1 -m32)
47+
48+
echo "::add-mask::$GARM_PASSWORD"
49+
echo "::add-mask::$REPO_WEBHOOK_SECRET"
50+
echo "::add-mask::$ORG_WEBHOOK_SECRET"
51+
52+
echo "GARM_PASSWORD=$GARM_PASSWORD" >> $GITHUB_ENV
53+
echo "REPO_WEBHOOK_SECRET=$REPO_WEBHOOK_SECRET" >> $GITHUB_ENV
54+
echo "ORG_WEBHOOK_SECRET=$ORG_WEBHOOK_SECRET" >> $GITHUB_ENV
55+
56+
- name: Create logs directory
57+
if: always()
58+
run: sudo mkdir -p /artifacts-logs && sudo chmod 777 /artifacts-logs
59+
60+
- name: Run integration tests
61+
run: |
62+
set -o pipefail
63+
set -o errexit
64+
go run ./test/integration/main.go 2>&1 | tee /artifacts-logs/e2e.log
65+
env:
66+
GARM_BASE_URL: ${{ steps.ngrok.outputs.tunnel-url }}
67+
ORG_NAME: gsamfira
68+
REPO_NAME: garm-testing
69+
CREDENTIALS_NAME: test-garm-creds
70+
WORKFLOW_FILE_NAME: test.yml
71+
GH_TOKEN: ${{ secrets.GH_OAUTH_TOKEN }}
72+
73+
- name: Show GARM logs
74+
if: always()
75+
run: |
76+
sudo systemctl status garm
77+
sudo journalctl -u garm --no-pager 2>&1 | tee /artifacts-logs/garm.log
78+
79+
- name: Upload GARM and e2e logs
80+
if: always()
81+
uses: actions/upload-artifact@v3
82+
with:
83+
name: garm-logs
84+
path: /artifacts-logs
85+
86+
- name: Cleanup orphan GARM resources via GitHub API
87+
if: always()
88+
run: |
89+
set -o pipefail
90+
set -o errexit
91+
92+
sudo systemctl stop garm
93+
94+
go run ./test/integration/gh_cleanup/main.go
95+
env:
96+
GARM_BASE_URL: ${{ steps.ngrok.outputs.tunnel-url }}
97+
ORG_NAME: gsamfira
98+
REPO_NAME: garm-testing
99+
GH_TOKEN: ${{ secrets.GH_OAUTH_TOKEN }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*.dll
55
*.so
66
*.dylib
7+
*.DS_Store
78

89
# Test binary, built with `go test -c`
910
*.test
@@ -16,3 +17,5 @@ bin/
1617
# vendor/
1718
.vscode
1819
cmd/temp
20+
build/
21+
release/

Dockerfile

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,28 @@ ADD . /build/garm
1010
RUN cd /build/garm && git checkout ${GARM_REF}
1111
RUN git clone https://github.com/cloudbase/garm-provider-azure /build/garm-provider-azure
1212
RUN git clone https://github.com/cloudbase/garm-provider-openstack /build/garm-provider-openstack
13+
RUN git clone https://github.com/cloudbase/garm-provider-lxd /build/garm-provider-lxd
14+
RUN git clone https://github.com/cloudbase/garm-provider-incus /build/garm-provider-incus
15+
RUN git clone https://github.com/mercedes-benz/garm-provider-k8s /build/garm-provider-k8s
1316

1417
RUN cd /build/garm && go build -o /bin/garm \
1518
-tags osusergo,netgo,sqlite_omit_load_extension \
16-
-ldflags "-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" \
19+
-ldflags "-extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" \
1720
/build/garm/cmd/garm
1821
RUN mkdir -p /opt/garm/providers.d
19-
RUN cd /build/garm-provider-azure && go build -ldflags="-linkmode external -extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-azure .
20-
RUN cd /build/garm-provider-openstack && go build -ldflags="-linkmode external -extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-openstack .
22+
RUN cd /build/garm-provider-azure && go build -ldflags="-extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-azure .
23+
RUN cd /build/garm-provider-openstack && go build -ldflags="-extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-openstack .
24+
RUN cd /build/garm-provider-lxd && go build -ldflags="-extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-lxd .
25+
RUN cd /build/garm-provider-incus && go build -ldflags="-extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-incus .
26+
RUN cd /build/garm-provider-k8s/cmd/garm-provider-k8s && go build -ldflags="-extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-k8s .
2127

2228
FROM scratch
2329

2430
COPY --from=builder /bin/garm /bin/garm
2531
COPY --from=builder /opt/garm/providers.d/garm-provider-openstack /opt/garm/providers.d/garm-provider-openstack
32+
COPY --from=builder /opt/garm/providers.d/garm-provider-lxd /opt/garm/providers.d/garm-provider-lxd
33+
COPY --from=builder /opt/garm/providers.d/garm-provider-incus /opt/garm/providers.d/garm-provider-incus
34+
COPY --from=builder /opt/garm/providers.d/garm-provider-k8s /opt/garm/providers.d/garm-provider-k8s
2635
COPY --from=builder /opt/garm/providers.d/garm-provider-azure /opt/garm/providers.d/garm-provider-azure
2736
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
2837

Dockerfile.build-static

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ RUN wget http://musl.cc/aarch64-linux-musl-cross.tgz -O /tmp/aarch64-linux-musl-
1212
ADD ./scripts/build-static.sh /build-static.sh
1313
RUN chmod +x /build-static.sh
1414

15+
ADD . /build/garm
16+
1517
CMD ["/bin/sh"]

Makefile

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,26 @@ USER_GROUP=$(shell ((docker --version | grep -q podman) && echo "0" || id -g))
77
ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
88
GOPATH ?= $(shell go env GOPATH)
99
VERSION ?= $(shell git describe --tags --match='v[0-9]*' --dirty --always)
10+
GARM_REF ?= $(shell git rev-parse --abbrev-ref HEAD)
1011
GO ?= go
1112

1213

1314
default: build
1415

15-
.PHONY : build-static test install-lint-deps lint go-test fmt fmtcheck verify-vendor verify
16+
.PHONY : build-static test install-lint-deps lint go-test fmt fmtcheck verify-vendor verify create-release-files release
1617
build-static:
1718
@echo Building garm
1819
docker build --tag $(IMAGE_TAG) -f Dockerfile.build-static .
19-
docker run --rm -e USER_ID=$(USER_ID) -e USER_GROUP=$(USER_GROUP) -v $(PWD):/build/garm:z $(IMAGE_TAG) /build-static.sh
20-
@echo Binaries are available in $(PWD)/bin
20+
docker run --rm -e USER_ID=$(USER_ID) -e GARM_REF=$(GARM_REF) -e USER_GROUP=$(USER_GROUP) -v $(PWD)/build:/build/output:z $(IMAGE_TAG) /build-static.sh
21+
@echo Binaries are available in $(PWD)/build
22+
23+
create-release-files:
24+
./scripts/make-release.sh
25+
26+
release: build-static create-release-files
27+
28+
clean:
29+
@rm -rf ./bin ./build ./release
2130

2231
build:
2332
@echo Building garm ${VERSION}

README.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
Welcome to GARM!
66

7-
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.
7+
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 auto-scaling that can be used inside your github workflow runs.
88

99
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.
1010

11-
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.
11+
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.
12+
13+
Through the use of providers, `GARM` can create runners in a variety of environments using the same `GARM` instance. Want to create pools of runners in your OpenStack cloud, your Azure cloud and your Kubernetes cluster? No problem! Just install the appropriate providers, configure them in `GARM` and you're good to go. Create zero-runner pools for instances with high costs (large VMs, GPU enabled instances, etc) and have them spin up on demand, or create large pools of k8s backed runners that can be used for your CI/CD pipelines at a moment's notice. You can mix them up and create pools in any combination of providers or resource allocations you want.
1214

1315
## Join us on slack
1416

@@ -18,14 +20,27 @@ Whether you're running into issues or just want to drop by and say "hi", feel fr
1820

1921
## Installing
2022

23+
### On virtual or physical machines
24+
2125
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.
2226

27+
### On Kubernetes
28+
29+
Thanks to the efforts of the amazing folks at @mercedes-benz, GARM can now be integrated into k8s via their operator. Check out the [GARM operator](https://github.com/mercedes-benz/garm-operator/) for more details.
30+
31+
## Supported providers
32+
33+
GARM uses providers to create runners in a particular IaaS. The providers are external executables that GARM calls into to create runners. Before you can create runners, you'll need to install at least one provider.
34+
2335
## Installing external providers
2436

25-
External providers are binaries that GARM calls into to create runners in a particular IaaS. There are currently two external providers available:
37+
External providers are binaries that GARM calls into to create runners in a particular IaaS. There are several external providers available:
2638

2739
* [OpenStack](https://github.com/cloudbase/garm-provider-openstack)
2840
* [Azure](https://github.com/cloudbase/garm-provider-azure)
41+
* [Kubernetes](https://github.com/mercedes-benz/garm-provider-k8s) - Thanks to the amazing folks at @mercedes-benz for sharing their awesome provider!
42+
* [LXD](https://github.com/cloudbase/garm-provider-lxd)
43+
* [Incus](https://github.com/cloudbase/garm-provider-incus)
2944

3045
Follow the instructions in the README of each provider to install them.
3146

@@ -47,8 +62,4 @@ If you would like to optimize the startup time of new instance, take a look at t
4762

4863
## Write your own provider
4964

50-
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.
51-
52-
There is currently one **native** provider for [LXD](https://linuxcontainers.org/lxd/) and two **external** providers for [Openstack and Azure](/contrib/providers.d/).
53-
54-
If you want to write your own provider, you can choose to write a native one, or implement an **external** one. The easiest one to write is probably an **external** provider. Please see the [Writing an external provider](/doc/external_provider.md) document for details. Also, feel free to inspect the two available external providers in this repository.
65+
The providers are interfaces between ```GARM``` and a particular IaaS in which we spin up GitHub Runners. **External** providers can be written in any language, as they are in the form of an external executable that ```GARM``` calls into. Please see the [Writing an external provider](/doc/external_provider.md) document for details. Also, feel free to inspect the two available sample external providers in this repository.

apiserver/controllers/controllers.go

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,16 @@ import (
3030
"github.com/cloudbase/garm/runner"
3131
wsWriter "github.com/cloudbase/garm/websocket"
3232

33+
"github.com/gorilla/mux"
3334
"github.com/gorilla/websocket"
3435
"github.com/pkg/errors"
3536
)
3637

3738
func NewAPIController(r *runner.Runner, authenticator *auth.Authenticator, hub *wsWriter.Hub) (*APIController, error) {
39+
controllerInfo, err := r.GetControllerInfo(auth.GetAdminContext())
40+
if err != nil {
41+
return nil, errors.Wrap(err, "failed to get controller info")
42+
}
3843
return &APIController{
3944
r: r,
4045
auth: authenticator,
@@ -43,18 +48,20 @@ func NewAPIController(r *runner.Runner, authenticator *auth.Authenticator, hub *
4348
ReadBufferSize: 1024,
4449
WriteBufferSize: 16384,
4550
},
51+
controllerID: controllerInfo.ControllerID.String(),
4652
}, nil
4753
}
4854

4955
type APIController struct {
50-
r *runner.Runner
51-
auth *auth.Authenticator
52-
hub *wsWriter.Hub
53-
upgrader websocket.Upgrader
56+
r *runner.Runner
57+
auth *auth.Authenticator
58+
hub *wsWriter.Hub
59+
upgrader websocket.Upgrader
60+
controllerID string
5461
}
5562

5663
func handleError(w http.ResponseWriter, err error) {
57-
w.Header().Add("Content-Type", "application/json")
64+
w.Header().Set("Content-Type", "application/json")
5865
origErr := errors.Cause(err)
5966
apiErr := params.APIErrorResponse{
6067
Details: origErr.Error(),
@@ -138,7 +145,19 @@ func (a *APIController) handleWorkflowJobEvent(w http.ResponseWriter, r *http.Re
138145
labelValues = a.webhookMetricLabelValues("true", "")
139146
}
140147

141-
func (a *APIController) CatchAll(w http.ResponseWriter, r *http.Request) {
148+
func (a *APIController) WebhookHandler(w http.ResponseWriter, r *http.Request) {
149+
vars := mux.Vars(r)
150+
controllerID, ok := vars["controllerID"]
151+
// If the webhook URL includes a controller ID, we validate that it's meant for us. We still
152+
// support bare webhook URLs, which are tipically configured manually by the user.
153+
// The controllerID suffixed webhook URL is useful when configuring the webhook for an entity
154+
// via garm. We cannot tag a webhook URL on github, so there is no way to determine ownership.
155+
// Using a controllerID suffix is a simple way to denote ownership.
156+
if ok && controllerID != a.controllerID {
157+
log.Printf("ignoring webhook meant for controller %s", util.SanitizeLogEntry(controllerID))
158+
return
159+
}
160+
142161
headers := r.Header.Clone()
143162

144163
event := runnerParams.Event(headers.Get("X-Github-Event"))
@@ -195,8 +214,9 @@ func (a *APIController) NotFoundHandler(w http.ResponseWriter, r *http.Request)
195214
Details: "Resource not found",
196215
Error: "Not found",
197216
}
198-
w.WriteHeader(http.StatusNotFound)
217+
199218
w.Header().Set("Content-Type", "application/json")
219+
w.WriteHeader(http.StatusNotFound)
200220
if err := json.NewEncoder(w).Encode(apiErr); err != nil {
201221
log.Printf("failet to write response: %q", err)
202222
}
@@ -377,3 +397,24 @@ func (a *APIController) ListAllJobs(w http.ResponseWriter, r *http.Request) {
377397
log.Printf("failed to encode response: %q", err)
378398
}
379399
}
400+
401+
// swagger:route GET /controller-info controllerInfo ControllerInfo
402+
//
403+
// Get controller info.
404+
//
405+
// Responses:
406+
// 200: ControllerInfo
407+
// 409: APIErrorResponse
408+
func (a *APIController) ControllerInfoHandler(w http.ResponseWriter, r *http.Request) {
409+
ctx := r.Context()
410+
info, err := a.r.GetControllerInfo(ctx)
411+
if err != nil {
412+
handleError(w, err)
413+
return
414+
}
415+
416+
w.Header().Set("Content-Type", "application/json")
417+
if err := json.NewEncoder(w).Encode(info); err != nil {
418+
log.Printf("failed to encode response: %q", err)
419+
}
420+
}

apiserver/controllers/instances.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"encoding/json"
1919
"log"
2020
"net/http"
21+
"strconv"
2122

2223
gErrors "github.com/cloudbase/garm-provider-common/errors"
2324
"github.com/cloudbase/garm/apiserver/params"
@@ -121,6 +122,12 @@ func (a *APIController) GetInstanceHandler(w http.ResponseWriter, r *http.Reques
121122
// in: path
122123
// required: true
123124
//
125+
// + name: forceRemove
126+
// description: If true GARM will ignore any provider error when removing the runner and will continue to remove the runner from github and the GARM database.
127+
// type: boolean
128+
// in: query
129+
// required: false
130+
//
124131
// Responses:
125132
// default: APIErrorResponse
126133
func (a *APIController) DeleteInstanceHandler(w http.ResponseWriter, r *http.Request) {
@@ -138,7 +145,8 @@ func (a *APIController) DeleteInstanceHandler(w http.ResponseWriter, r *http.Req
138145
return
139146
}
140147

141-
if err := a.r.ForceDeleteRunner(ctx, instanceName); err != nil {
148+
forceRemove, _ := strconv.ParseBool(r.URL.Query().Get("forceRemove"))
149+
if err := a.r.DeleteRunner(ctx, instanceName, forceRemove); err != nil {
142150
log.Printf("removing runner: %s", err)
143151
handleError(w, err)
144152
return
@@ -316,19 +324,3 @@ func (a *APIController) InstanceStatusMessageHandler(w http.ResponseWriter, r *h
316324
w.Header().Set("Content-Type", "application/json")
317325
w.WriteHeader(http.StatusOK)
318326
}
319-
320-
func (a *APIController) InstanceGithubRegistrationTokenHandler(w http.ResponseWriter, r *http.Request) {
321-
ctx := r.Context()
322-
323-
token, err := a.r.GetInstanceGithubRegistrationToken(ctx)
324-
if err != nil {
325-
handleError(w, err)
326-
return
327-
}
328-
329-
w.Header().Set("Content-Type", "application/json")
330-
w.WriteHeader(http.StatusOK)
331-
if _, err := w.Write([]byte(token)); err != nil {
332-
log.Printf("failed to encode response: %q", err)
333-
}
334-
}

0 commit comments

Comments
 (0)