Skip to content

Commit 13d9b18

Browse files
committedAug 2, 2024·
Introduce jit config runner registration
1 parent 93614d7 commit 13d9b18

File tree

7 files changed

+237
-79
lines changed

7 files changed

+237
-79
lines changed
 

‎cloudRun.tf

+6-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ resource "google_cloud_run_v2_service" "autoscaler" {
5252
value = var.github_runner_prefix
5353
}
5454
env {
55-
name = "RUNNER_GROUP"
56-
value = var.github_runner_group
55+
name = "RUNNER_GROUP_NAME"
56+
value = var.github_runner_group_name
57+
}
58+
env {
59+
name = "RUNNER_GROUP_ID"
60+
value = var.github_runner_group_id
5761
}
5862
env {
5963
name = "RUNNER_LABELS"

‎compute.tf

+35-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ resource "google_compute_instance_template" "runner_instance" {
4040
}
4141
}
4242

43+
// First parameter has to be the registration token
4344
resource "google_compute_project_metadata_item" "startup_scripts_register_runner" {
4445
key = "startup_script_register_runner"
4546
value = <<EOT
@@ -49,13 +50,45 @@ apt-get update && apt-get -y install docker.io docker-buildx curl
4950
useradd -d /home/agent -u 10000 agent
5051
usermod -aG docker agent
5152
newgrp docker
52-
wget -q -O /tmp/agent.tar.gz '${var.github_runner_download_url}'
53+
curl -s -o /tmp/agent.tar.gz -L '${var.github_runner_download_url}'
5354
mkdir -p /home/agent
5455
chown -R agent:agent /home/agent
5556
pushd /home/agent
5657
sudo -u agent tar zxf /tmp/agent.tar.gz
5758
registration_token=$1
58-
sudo -u agent ./config.sh --unattended --disableupdate --ephemeral --name $(hostname) ${local.runnerLabelInstanceTemplate} --url 'https://github.com/${var.github_organization}' --token $${registration_token} --runnergroup '${var.github_runner_group}' || shutdown now
59+
sudo -u agent ./config.sh --unattended --disableupdate --ephemeral --name $(hostname) ${local.runnerLabelInstanceTemplate} --url 'https://github.com/${var.github_organization}' --token $${registration_token} --runnergroup '${var.github_runner_group_name}' || shutdown now
60+
./bin/installdependencies.sh || shutdown now
61+
./svc.sh install agent || shutdown now
62+
./svc.sh start || shutdown now
63+
popd
64+
rm /tmp/agent.tar.gz
65+
echo "Setup finished"
66+
EOT
67+
}
68+
69+
// First parameter has to be the base64 encoded jit_config
70+
resource "google_compute_project_metadata_item" "startup_scripts_register_jit_runner" {
71+
key = "startup_script_register_jit_runner"
72+
value = <<EOT
73+
#!/bin/bash
74+
agent_name=$(hostname)
75+
echo "Setup of agent '$agent_name' started"
76+
apt-get update && apt-get -y install docker.io docker-buildx curl jq
77+
useradd -d /home/agent -u 10000 agent
78+
usermod -aG docker agent
79+
newgrp docker
80+
curl -s -o /tmp/agent.tar.gz -L '${var.github_runner_download_url}'
81+
mkdir -p /home/agent
82+
chown -R agent:agent /home/agent
83+
pushd /home/agent
84+
sudo -u agent tar zxf /tmp/agent.tar.gz
85+
encoded_jit_config=$1
86+
echo -n $encoded_jit_config | base64 -d | jq '.".runner"' -r | base64 -d > .runner
87+
echo -n $encoded_jit_config | base64 -d | jq '.".credentials"' -r | base64 -d > .credentials
88+
echo -n $encoded_jit_config | base64 -d | jq '.".credentials_rsaparams"' -r | base64 -d > .credentials_rsaparams
89+
sed -i 's/{{SvcNameVar}}/actions.runner.${var.github_organization}.$${agent_name}.service/g' bin/systemd.svc.sh.template
90+
sed -i 's/{{SvcDescription}}/GitHub Actions Runner (${var.github_organization}.$${agent_name})/g' bin/systemd.svc.sh.template
91+
cp bin/systemd.svc.sh.template ./svc.sh && chmod +x ./svc.sh
5992
./bin/installdependencies.sh || shutdown now
6093
./svc.sh install agent || shutdown now
6194
./svc.sh start || shutdown now

‎runner-autoscaler/README.md

+21-17
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,30 @@ Following conditions of the workflow job webhook event have to be fulfilled, so
1919

2020
* The webhook signature is valid (see WEBHOOK_SECRET env).
2121
* The webhook `action` value equals `completed`.
22-
* The webhook `workflow_job.runner_group_name` value equals the configured RUNNER_GROUP.
22+
* The webhook `workflow_job.runner_group_name` value equals the configured RUNNER_GROUP_NAME.
2323
* **All** labels of the workflow job match the configured RUNNER_LABELS.
2424

2525
### Configuration
2626

2727
The scaler is configured via the following environment variables:
2828

29-
| Env | Default | Description |
30-
| ----------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31-
| ROUTE_WEBHOOK | /webhook | The Cloud Run path that is invoked by the GitHub webhook. Depending on the workflow job, a Cloud Task "delete runner" or "create runner" is enqueued. |
32-
| ROUTE_DELETE_VM | /delete_vm | The Cloud Run callback path invoked by Cloud Task when a VM instance should be **deleted**. The payload contains the name of the "to be deleted" VM instance. |
33-
| ROUTE_CREATE_VM | /create_vm | The Cloud Run callback path invoked by Cloud Task when a VM instance should be **created**. The payload contains the name of the "to be created" VM instance. |
34-
| WEBHOOK_SECRET | | The GitHub webhook secret. This is the secret the webhook has been [configured](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) with. |
35-
| PROJECT_ID | | The Google Cloud Project Id. |
36-
| ZONE | | The Google Cloud zone where the VM instance will be created. |
37-
| TASK_QUEUE | | The relative resource name of the Cloud Task queue. |
38-
| INSTANCE_TEMPLATE | | The relative resource name of the instance template from which the VM instance will be created. |
39-
| SECRET_VERSION | | The relative resource name of the secret version which contains the PAT |
40-
| RUNNER_PREFIX | runner | Prefix for the the name of a new VM instance. A random string (10 random lower case characters) will be added to make the name unique: "<prefix>-<random_string>". |
41-
| RUNNER_GROUP | Default | The GitHub runner group where the VM instance is expected to join as a self hosted runner. |
42-
| RUNNER_LABELS | self-hosted *(comma separated list)* | Only workflow jobs whose labels match **all** the configured labels will be taken into account. If only one configured label is **not** found in the workflow job it will be ignored. |
43-
| GITHUB_ORG | | The name of the GitHub Organization |
44-
| PORT | 8080 | To which port the webserver is bound. |
29+
| Env | Default | Description |
30+
| ----------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
31+
| ROUTE_WEBHOOK | /webhook | The Cloud Run path that is invoked by the GitHub webhook. Depending on the workflow job, a Cloud Task "delete runner" or "create runner" is enqueued. |
32+
| ROUTE_DELETE_VM | /delete_vm | The Cloud Run callback path invoked by Cloud Task when a VM instance should be **deleted**. The payload contains the name of the "to be deleted" VM instance. |
33+
| ROUTE_CREATE_VM | /create_vm | The Cloud Run callback path invoked by Cloud Task when a VM instance should be **created**. The payload contains the name of the "to be created" VM instance. |
34+
| WEBHOOK_SECRET | | The GitHub webhook secret. This is the secret the webhook has been [configured](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) with. |
35+
| PROJECT_ID | | The Google Cloud Project Id. |
36+
| ZONE | | The Google Cloud zone where the VM instance will be created. |
37+
| TASK_QUEUE | | The relative resource name of the Cloud Task queue. |
38+
| INSTANCE_TEMPLATE | | The relative resource name of the instance template from which the VM instance will be created. |
39+
| SECRET_VERSION | | The relative resource name of the secret version which contains the PAT |
40+
| RUNNER_PREFIX | runner | Prefix for the the name of a new VM instance. A random string (10 random lower case characters) will be added to make the name unique: "<prefix>-<random_string>". |
41+
| RUNNER_GROUP_NAME | Default | The GitHub runner group name where the VM instance is expected to join as a self hosted runner. |
42+
| RUNNER_GROUP_ID | 0 | (optional - but preferred) The GitHub runner group ID where the VM instance is expected to join as a self hosted runner (must be the ID of the **same** runner group with the name RUNNER_GROUP_NAME). |
43+
| RUNNER_LABELS | self-hosted *(comma separated list)* | Only workflow jobs whose labels match **all** the configured labels will be taken into account. If only one configured label is **not** found in the workflow job it will be ignored. |
44+
| GITHUB_ORG | | The name of the GitHub Organization |
45+
| PORT | 8080 | To which port the webserver is bound. |
46+
47+
> [!IMPORTANT]
48+
> If RUNNER_GROUP_ID is set to a value **greater than 0** the VM instance will use a **jit config** to register itself as a GitHub runner. Else the VM instance will use a registration token to register itself as a GitHub runner.

‎runner-autoscaler/main.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ func main() {
3838
logrus.SetLevel(logrus.InfoLevel)
3939

4040
labels := strings.Split(getEnvDefault("RUNNER_LABELS", "self-hosted"), ",")
41-
runnerGroup := getEnvDefault("RUNNER_GROUP", "Default")
41+
runnerGroup := getEnvDefault("RUNNER_GROUP_NAME", "Default")
42+
runnerGroupId, _ := strconv.Atoi(getEnvDefault("RUNNER_GROUP_ID", "0"))
4243
scaler := pkg.NewAutoscaler(pkg.AutoscalerConfig{
4344
RouteWebhook: getEnvDefault("ROUTE_WEBHOOK", "/webhook"),
4445
RouteDeleteVm: getEnvDefault("ROUTE_DELETE_VM", "/delete_vm"),
@@ -50,7 +51,8 @@ func main() {
5051
InstanceTemplate: mustGetEnv("INSTANCE_TEMPLATE"),
5152
SecretVersion: mustGetEnv("SECRET_VERSION"),
5253
RunnerPrefix: getEnvDefault("RUNNER_PREFIX", "runner"),
53-
RunnerGroup: runnerGroup,
54+
RunnerGroupName: runnerGroup,
55+
RunnerGroupId: runnerGroupId,
5456
RunnerLabels: labels,
5557
GitHubOrg: mustGetEnv("GITHUB_ORG"),
5658
})

‎runner-autoscaler/pkg/srv.go

+151-52
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pkg
22

33
import (
4+
"bytes"
45
"context"
56
"crypto/hmac"
67
"crypto/sha256"
@@ -26,6 +27,7 @@ import (
2627
"google.golang.org/protobuf/types/known/timestamppb"
2728
)
2829

30+
const GITHUB_API_VERSION string = "2022-11-28"
2931
const SHA_PREFIX string = "sha256="
3032
const SHA_HEADER string = "x-hub-signature-256"
3133
const EVENT_HEADER string = "x-github-event"
@@ -34,9 +36,13 @@ const WEBHOOK_PING_EVENT string = "ping"
3436
const WEBHOOK_JOB_EVENT string = "workflow_job"
3537

3638
const RUNNER_REGISTRATION_TOKEN_ATTR string = "registration_token"
37-
const RUNNER_STARTUP_SCRIPT_ATTR string = "startup_script_register_runner"
39+
const RUNNER_JIT_CONFIG_ATTR string = "jit_config"
40+
41+
const RUNNER_SCRIPT_REGISTER_RUNNER_ATTR string = "startup_script_register_runner" // has to match the global custom metadata in compute.tf
42+
const RUNNER_SCRIPT_REGISTER_JIT_RUNNER_ATTR string = "startup_script_register_jit_runner" // has to match the global custom metadata in compute.tf
3843

3944
const RUNNER_REGISTER_TOKEN_ORG_ENDPOINT string = "https://api.github.com/orgs/%s/actions/runners/registration-token"
45+
const RUNNER_JIT_CONFIG_ENDPOINT string = "https://api.github.com/orgs/%s/actions/runners/generate-jitconfig"
4046

4147
type Job struct {
4248
Id int64 `json:"id"`
@@ -148,7 +154,7 @@ func newSecretAccessClient(ctx context.Context) *secretmanager.Client {
148154

149155
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz")
150156

151-
func randStringRunes(n int) string {
157+
func RandStringRunes(n int) string {
152158

153159
b := make([]rune, n)
154160
for i := range b {
@@ -305,7 +311,7 @@ func (s *Autoscaler) CreateInstanceFromTemplate(ctx context.Context, instanceNam
305311
return nil
306312
}
307313

308-
func (s *Autoscaler) GenerateRunnerRegistrationToken(ctx context.Context) (string, error) {
314+
func (s *Autoscaler) readPat(ctx context.Context) (string, error) {
309315

310316
log.Debugf("About to request GitHub runner registration token using PAT from secret version: %s", s.conf.SecretVersion)
311317
secretAccessClient := newSecretAccessClient(ctx)
@@ -320,34 +326,93 @@ func (s *Autoscaler) GenerateRunnerRegistrationToken(ctx context.Context) (strin
320326
log.Errorf("The GitHub PAT secret is empty")
321327
return "", fmt.Errorf("empty GitHub PAT")
322328
} else {
323-
if req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf(RUNNER_REGISTER_TOKEN_ORG_ENDPOINT, s.conf.GitHubOrg), nil); err != nil {
324-
log.Errorf("Could not create GitHub runner registration token request")
325-
return "", fmt.Errorf("failed registration token request")
329+
return pat, nil
330+
}
331+
}
332+
}
333+
334+
func (s *Autoscaler) GenerateRunnerRegistrationToken(ctx context.Context) (string, error) {
335+
336+
log.Debugf("About to request GitHub runner registration token using PAT from secret version: %s", s.conf.SecretVersion)
337+
secretAccessClient := newSecretAccessClient(ctx)
338+
defer secretAccessClient.Close()
339+
if pat, err := s.readPat(ctx); err != nil {
340+
return "", err
341+
} else {
342+
if req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf(RUNNER_REGISTER_TOKEN_ORG_ENDPOINT, s.conf.GitHubOrg), nil); err != nil {
343+
log.Errorf("Could not create GitHub runner registration token request")
344+
return "", fmt.Errorf("failed registration token request")
345+
} else {
346+
req.Header.Add("Accept", "application/vnd.github+json")
347+
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", pat))
348+
req.Header.Add("X-GitHub-Api-Version", GITHUB_API_VERSION)
349+
req.Header.Add("User-Agent", "github-runner-autoscaler")
350+
if resp, err := http.DefaultClient.Do(req); err != nil {
351+
log.Errorf("GitHub runner registration token request failed: %s", err.Error())
352+
return "", fmt.Errorf("failed registration token response")
353+
} else if resp.StatusCode != 201 {
354+
log.Errorf("GitHub runner registration token request unsuccessful: %s", resp.Status)
355+
defer resp.Body.Close()
356+
return "", fmt.Errorf("failed registration token response")
326357
} else {
327-
req.Header.Add("Accept", "application/vnd.github+json")
328-
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", pat))
329-
req.Header.Add("X-GitHub-Api-Version", "2022-11-28")
330-
req.Header.Add("User-Agent", "github-runner-autoscaler")
331-
if resp, err := http.DefaultClient.Do(req); err != nil {
332-
log.Errorf("GitHub runner registration token request failed: %s", err.Error())
358+
defer resp.Body.Close()
359+
body, _ := io.ReadAll(resp.Body)
360+
payload := map[string]string{}
361+
if err := json.Unmarshal(body, &payload); err != nil {
362+
log.Errorf("GitHub runner registration token response missing: %s", err.Error())
333363
return "", fmt.Errorf("failed registration token response")
334-
} else if resp.StatusCode != 201 {
335-
log.Errorf("GitHub runner registration token request unsuccessful: %s", resp.Status)
336-
defer resp.Body.Close()
364+
} else if token, ok := payload["token"]; ok && len(token) > 0 {
365+
return token, nil
366+
} else {
367+
log.Errorf("GitHub runner registration token is empty")
337368
return "", fmt.Errorf("failed registration token response")
369+
}
370+
}
371+
}
372+
}
373+
}
374+
375+
func (s *Autoscaler) GenerateRunnerJitConfig(ctx context.Context, runnerName string) (string, error) {
376+
377+
log.Debugf("About to request GitHub runner jit config using PAT from secret version: %s", s.conf.SecretVersion)
378+
secretAccessClient := newSecretAccessClient(ctx)
379+
defer secretAccessClient.Close()
380+
if pat, err := s.readPat(ctx); err != nil {
381+
return "", err
382+
} else {
383+
reqPayload := map[string]any{}
384+
reqPayload["name"] = runnerName
385+
reqPayload["runner_group_id"] = s.conf.RunnerGroupId
386+
reqPayload["labels"] = s.conf.RunnerLabels
387+
reqPayload["work_folder"] = "_work"
388+
data, _ := json.Marshal(reqPayload)
389+
if req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf(RUNNER_JIT_CONFIG_ENDPOINT, s.conf.GitHubOrg), bytes.NewReader(data)); err != nil {
390+
log.Errorf("Could not create GitHub runner jit-config request")
391+
return "", fmt.Errorf("failed jit-config request")
392+
} else {
393+
req.Header.Add("Accept", "application/vnd.github+json")
394+
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", pat))
395+
req.Header.Add("X-GitHub-Api-Version", GITHUB_API_VERSION)
396+
req.Header.Add("User-Agent", "github-runner-autoscaler")
397+
if resp, err := http.DefaultClient.Do(req); err != nil {
398+
log.Errorf("GitHub runner jit-config request failed: %s", err.Error())
399+
return "", fmt.Errorf("failed jit-config response")
400+
} else if resp.StatusCode != 201 {
401+
log.Errorf("GitHub runner jit-config request unsuccessful: %s", resp.Status)
402+
defer resp.Body.Close()
403+
return "", fmt.Errorf("failed jit-config response")
404+
} else {
405+
defer resp.Body.Close()
406+
body, _ := io.ReadAll(resp.Body)
407+
payload := map[string]any{}
408+
if err := json.Unmarshal(body, &payload); err != nil {
409+
log.Errorf("GitHub runner jit-config response missing: %s", err.Error())
410+
return "", fmt.Errorf("failed jit-config response")
411+
} else if jitConfig, ok := payload["encoded_jit_config"].(string); ok && len(jitConfig) > 0 {
412+
return jitConfig, nil
338413
} else {
339-
defer resp.Body.Close()
340-
body, _ := io.ReadAll(resp.Body)
341-
payload := map[string]string{}
342-
if err := json.Unmarshal(body, &payload); err != nil {
343-
log.Errorf("GitHub runner registration token response missing: %s", err.Error())
344-
return "", fmt.Errorf("failed registration token response")
345-
} else if token, ok := payload["token"]; ok && len(token) > 0 {
346-
return token, nil
347-
} else {
348-
log.Errorf("GitHub runner registration token is empty")
349-
return "", fmt.Errorf("failed registration token response")
350-
}
414+
log.Errorf("GitHub runner jit-config is empty")
415+
return "", fmt.Errorf("failed jit-config response")
351416
}
352417
}
353418
}
@@ -392,32 +457,65 @@ func (s *Autoscaler) createCallbackTaskWithToken(ctx context.Context, url, messa
392457
return nil
393458
}
394459

460+
const runner_script_wrapper = `
461+
#!/bin/bash
462+
val=$(curl "http://metadata.google.internal/computeMetadata/v1/instance/attributes/%s" -H "Metadata-Flavor: Google")
463+
curl "http://metadata.google.internal/computeMetadata/v1/project/attributes/%s" -H "Metadata-Flavor: Google" > runner_startup.sh
464+
chmod +x ./runner_startup.sh
465+
./runner_startup.sh $val
466+
rm runner_startup.sh
467+
`
468+
469+
func (s *Autoscaler) createVmWithRegistrationToken(ctx *gin.Context, instanceName string) {
470+
if token, err := s.GenerateRunnerRegistrationToken(ctx); err != nil {
471+
ctx.AbortWithError(http.StatusInternalServerError, err)
472+
} else {
473+
registration_token_attr := fmt.Sprintf("%s_%s", RUNNER_REGISTRATION_TOKEN_ATTR, RandStringRunes(16))
474+
if err := s.CreateInstanceFromTemplate(ctx, instanceName, &computepb.Items{
475+
Key: proto.String(registration_token_attr),
476+
Value: proto.String(token),
477+
}, &computepb.Items{
478+
Key: proto.String("startup-script"),
479+
Value: proto.String(fmt.Sprintf(runner_script_wrapper, registration_token_attr, RUNNER_SCRIPT_REGISTER_RUNNER_ATTR)),
480+
}); err != nil {
481+
ctx.AbortWithError(http.StatusInternalServerError, err)
482+
} else {
483+
ctx.Status(http.StatusOK)
484+
}
485+
}
486+
}
487+
488+
func (s *Autoscaler) createVmWithJitConfig(ctx *gin.Context, instanceName string) {
489+
if jitConfig, err := s.GenerateRunnerJitConfig(ctx, instanceName); err != nil {
490+
ctx.AbortWithError(http.StatusInternalServerError, err)
491+
} else {
492+
jit_config_attr := fmt.Sprintf("%s_%s", RUNNER_JIT_CONFIG_ATTR, RandStringRunes(16))
493+
if err := s.CreateInstanceFromTemplate(ctx, instanceName, &computepb.Items{
494+
Key: proto.String(jit_config_attr),
495+
Value: proto.String(jitConfig),
496+
}, &computepb.Items{
497+
Key: proto.String("startup-script"),
498+
Value: proto.String(fmt.Sprintf(runner_script_wrapper, jit_config_attr, RUNNER_SCRIPT_REGISTER_JIT_RUNNER_ATTR)),
499+
}); err != nil {
500+
ctx.AbortWithError(http.StatusInternalServerError, err)
501+
} else {
502+
ctx.Status(http.StatusOK)
503+
}
504+
}
505+
}
506+
395507
func (s *Autoscaler) handleCreateVm(ctx *gin.Context) {
396508

397509
log.Info("Received create-vm cloud task callback")
398510
if data, err := s.verifySignature(ctx); err == nil {
399-
if token, err := s.GenerateRunnerRegistrationToken(ctx); err != nil {
400-
ctx.AbortWithError(http.StatusInternalServerError, err)
511+
if s.conf.RunnerGroupId > 0 {
512+
// use jit config
513+
log.Info("Using jit config for runner registration")
514+
s.createVmWithJitConfig(ctx, string(data))
401515
} else {
402-
registration_token_attr := fmt.Sprintf("%s_%s", RUNNER_REGISTRATION_TOKEN_ATTR, randStringRunes(16))
403-
if err := s.CreateInstanceFromTemplate(ctx, string(data), &computepb.Items{
404-
Key: proto.String(registration_token_attr),
405-
Value: proto.String(token),
406-
}, &computepb.Items{
407-
Key: proto.String("startup-script"),
408-
Value: proto.String(fmt.Sprintf(`
409-
#!/bin/bash
410-
registration_token=$(curl "http://metadata.google.internal/computeMetadata/v1/instance/attributes/%s" -H "Metadata-Flavor: Google")
411-
curl "http://metadata.google.internal/computeMetadata/v1/project/attributes/%s" -H "Metadata-Flavor: Google" > runner_startup.sh
412-
chmod +x ./runner_startup.sh
413-
./runner_startup.sh $registration_token
414-
rm runner_startup.sh
415-
`, registration_token_attr, RUNNER_STARTUP_SCRIPT_ATTR)),
416-
}); err != nil {
417-
ctx.AbortWithError(http.StatusInternalServerError, err)
418-
} else {
419-
ctx.Status(http.StatusOK)
420-
}
516+
// use registration token
517+
log.Info("Using registration token for runner registration")
518+
s.createVmWithRegistrationToken(ctx, string(data))
421519
}
422520
}
423521
}
@@ -452,7 +550,7 @@ func (s *Autoscaler) handleWebhook(ctx *gin.Context) {
452550
if payload.Action == QUEUED {
453551
if ok, missingLabels := payload.Job.hasAllLabels(s.conf.RunnerLabels); ok {
454552
createUrl := createCallbackUrl(ctx, s.conf.RouteCreateVm)
455-
if err := s.createCallbackTaskWithToken(ctx, createUrl, fmt.Sprintf("%s-%s", s.conf.RunnerPrefix, randStringRunes(10))); err != nil {
553+
if err := s.createCallbackTaskWithToken(ctx, createUrl, fmt.Sprintf("%s-%s", s.conf.RunnerPrefix, RandStringRunes(10))); err != nil {
456554
log.Errorf("Can not enqueue create-vm cloud task callback: %s", err.Error())
457555
ctx.AbortWithError(http.StatusInternalServerError, err)
458556
return
@@ -461,7 +559,7 @@ func (s *Autoscaler) handleWebhook(ctx *gin.Context) {
461559
log.Warnf("Webhook requested to start a runner that is missing the label(s) \"%s\" - ignoring", strings.Join(missingLabels, ", "))
462560
}
463561
} else if payload.Action == COMPLETED {
464-
if payload.Job.RunnerGroupName == s.conf.RunnerGroup {
562+
if payload.Job.RunnerGroupName == s.conf.RunnerGroupName {
465563
if ok, missingLabels := payload.Job.hasAllLabels(s.conf.RunnerLabels); ok {
466564
deleteUrl := createCallbackUrl(ctx, s.conf.RouteDeleteVm)
467565
if err := s.createCallbackTaskWithToken(ctx, deleteUrl, payload.Job.RunnerName); err != nil {
@@ -473,7 +571,7 @@ func (s *Autoscaler) handleWebhook(ctx *gin.Context) {
473571
log.Warnf("Webhook signaled to delete a runner that is missing the label(s) \"%s\" - ignoring", strings.Join(missingLabels, ", "))
474572
}
475573
} else {
476-
log.Warnf("Webhook signaled to delete a runner that does not belong to the expected runner group (expected \"%s\" got \"%s\") - ignoring", s.conf.RunnerGroup, payload.Job.RunnerGroupName)
574+
log.Warnf("Webhook signaled to delete a runner that does not belong to the expected runner group (expected \"%s\" got \"%s\") - ignoring", s.conf.RunnerGroupName, payload.Job.RunnerGroupName)
477575
}
478576
}
479577
ctx.Status(http.StatusOK)
@@ -496,7 +594,8 @@ type AutoscalerConfig struct {
496594
InstanceTemplate string
497595
SecretVersion string
498596
RunnerPrefix string
499-
RunnerGroup string
597+
RunnerGroupName string
598+
RunnerGroupId int
500599
RunnerLabels []string
501600
GitHubOrg string
502601
}

‎runner-autoscaler/test/main_test.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ func init() {
3434
InstanceTemplate: "projects/" + PROJECT_ID + "/global/instanceTemplates/ephemeral-github-runner",
3535
SecretVersion: "projects/" + PROJECT_ID + "/secrets/github-pat-token/versions/latest",
3636
RunnerPrefix: "runner",
37-
RunnerGroup: "Default",
37+
RunnerGroupName: "Default",
38+
RunnerGroupId: 1,
3839
RunnerLabels: []string{"self-hosted"},
3940
GitHubOrg: GIT_HUB_ORG,
4041
})
@@ -43,7 +44,7 @@ func init() {
4344

4445
func TestWebhookSignature(t *testing.T) {
4546

46-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
47+
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
4748
defer cancel()
4849
req, _ := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://127.0.0.1:%d/webhook", PORT), strings.NewReader("Hello, World!"))
4950
req.Header.Add("x-hub-signature-256", "sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17")
@@ -55,9 +56,18 @@ func TestWebhookSignature(t *testing.T) {
5556

5657
func TestGenerateRunnerRegistrationToken(t *testing.T) {
5758

58-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
59+
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
5960
defer cancel()
6061
token, err := scaler.GenerateRunnerRegistrationToken(ctx)
6162
assert.Nil(t, err)
6263
assert.NotEmpty(t, token)
6364
}
65+
66+
func TestGenerateRunnerJitConfig(t *testing.T) {
67+
68+
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
69+
defer cancel()
70+
jitConfig, err := scaler.GenerateRunnerJitConfig(ctx, "unit_test_test_runner_"+pkg.RandStringRunes(10))
71+
assert.Nil(t, err)
72+
assert.NotEmpty(t, jitConfig)
73+
}

‎variables.tf

+7-1
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,18 @@ variable "github_organization" {
3939
description = "The name of the GitHub organization the runner will join"
4040
}
4141

42-
variable "github_runner_group" {
42+
variable "github_runner_group_name" {
4343
type = string
4444
description = "The name of the GitHub runner group the runner will join"
4545
default = "Default"
4646
}
4747

48+
variable "github_runner_group_id" {
49+
type = number
50+
description = "The ID of the GitHub runner group the runner will join (must be the same group with name var.github_runner_group_name). If this value is greater 0 then a jit config is used for runner registration. Otherwise a registration token is used!"
51+
default = 0
52+
}
53+
4854
variable "github_runner_labels" {
4955
type = list(string)
5056
description = "One or multiple labels the runner will be tagged with"

0 commit comments

Comments
 (0)
Please sign in to comment.